[JAVA] I want to summarize Apache Wicket 8 because it is a good idea

Postscript

After the official release, I summarized it in What happened to the typical changes of Apache Wicket 8.


This article is for 12/23 of Java EE Advent Calendar 2016.

Yesterday, @ HirofumiIwasaki's "Future image of Java EE 8 (revised) through JavaOne 2016" -2016-java-ee-advent-calendar /). Tomorrow is @kikutaro.

Introduction

By the way, Java component-oriented web frameworks, which are rarely hot topics these days, are typical ones that have been continuously developed since the latter half of the 2000s, such as JSF, Apache Wicket, and Vaadin. Can be mentioned.

Most of the Apache Wicket use cases published on Twitter accounts, etc. are from overseas, but in Japan, [Tagajo City Archive Site](http:: There are reports that it is also used in //tagajo.irides.tohoku.ac.jp/index) and National Police Agency site opinion box. I will. Wicket itself is also being continuously developed by the community, and development versions of Wicket 8.0 for Java 8 are beginning to be released.

Wicket 8.0 makes Java 8 Lambda expressions available in Wicket components and models. For Wicket users who have fallen in love with the stance of a web framework that is easy for Java programmers to develop, this is a long-awaited improvement while wondering "when will it come?"

Against this background, in this article, Wicket 8 Migration Guide: Migration to Wicket 8.0 and [ I would like to summarize the changes in the way code is written from the upcoming Wicket 8.0 release, with reference to Documents by the committer at ApacheCon EU 2016.

The content of the article is based on Wicket 8.0.0-M2 at the time of writing, and is subject to change in the future.

Change of operating environment

Wicket 8.0 requires at least Java 8 Wicket 8.0 requires Servlet 3.1 (Jetty 9.2+, Apache Tomcat 8+, JBoss WildFly 10+)

(From Migration to Wicket 8.0)

With Wicket 8.0, Java 8 is a required environment. Along with this, the base Servlet has also been upgraded to 3.1, so it is necessary to pay attention to the supported version of the Servlet container [^ 1].

[^ 1]: But web.xml still remains ...

Model changes

LambdaModel

A major change is the addition of the Lambda Model.

As the name implies, this is a ** Model that allows you to specify getter / setter processing for ModelObject in a Java 8 Lambda expression or method reference.

// Wicket 8.Example of Lambda Model from 0
Order order = new Order();
IModel<String> model = LambdaModel.of(order::getUserName, order::setUserName);

In this way, the method reference of the first argument is treated as getModelObject (), and the method reference of the second argument is treated as the processing inside the setModelObject (String) process.

This Model seems to have the intent to be used as an alternative to ** PropertyModel **.

//Examples of Property Models so far
Order order = new Order();
IModel<String> model = new PropertyModel(order, "userName");

PropertyModel is convenient because it can be linked with instance fields and setters / getters, but on the other hand, it is troublesome to create and modify it because the referenced field variable must be specified as a character string. If you use LambdaModel, you can solve this trouble and do the same thing as it is [^ 2].

[^ 2]: Personally, I use CompoundPropertyModel anyway ...

Model settings in the component

Components such as Label work by passing a Model (or an object inside it). ** Lambda expressions and method references can also be used when passing a component's Model or Object **.

Order order = new Order();

//Until now (even if you pass getUserName directly, it will wrap in Model internally)
add(new Label("foo", Model.of(order.getUserName())));
add(new Label("foo", order.getUserName()));

// Wicket 8.From 0, you can pass Model or Object with method reference or Lambda expression
add(new Label("foo", order::getUserName));
add(new Label("foo", () -> {...}));

Model factory method

** Model now has a factory method (of) as standard **. You can use Lambda expressions and method references in of.

IService service = new Service();

//So far
IModel<List<Dish>> model = new LoadableDetachableModel<List<Dish>>() {
  private static final long serialVersionUID = 1380883852804223557L;

  @Override
  protected List<Dish> load() {
    return service.fetchDishes();
  }
};

// Wicket 8.How to write from 0. I am very happy that the LoadableDetachableModel is on one line.
IModel<List<Dish>> model = LoadableDetachableModel.of(service::fetchDishes);

In this way, you can write a LoadableDetachableModel etc. in one line, which is convenient depending on the usage, but you had to make it an anonymous class every time **. This is very nice. Personally, I felt that this was the biggest advantage among the changes in Model.

However, on the other hand, ** AbstractReadOnlyModel has been deprecated, and it is now recommended to use IModel's anonymous class instead **. When migrating to Wicket 8.0, it seems that it will be necessary to search and replace all at once.

IService service = new Service();

//So far
IModel<Integer> model = new AbstractReadOnlyModel<Integer>() {
  private static final long serialVersionUID = 2631631544271526736L;

  @Override
  public Integer getObject() {
    return new Random().nextInt();
  }
};

// Wicket 8.From 0, AbstractReadOnlyModel is deprecated. Switch to an anonymous class in IModel.
IModel<Integer> model = new IModel<Integer>() {
  private static final long serialVersionUID = -2636918853427797619L;

  @Override
  public Integer getObject() {
    return new Random().nextInt();
  }
};

Component changes

Use of lambda expressions in Link and Button

For Link that moves between pages and Button component that posts a value with Foam, it was common to make it an anonymous class in order to override the onClick () and onSubmit () methods that control the click event.

** In Wicket8, by using Lambda expression, you can create Links and Buttons without making anonymous classes **.

//So far
Link<Void> link = new Link<Void>("toHogePage") {
  private static final long serialVersionUID = 3391006051307639762L;

  @Override
  public void onClick() {
    //Various processing
    setResponsePage(new HogePage(model));
  }
};

// Wicket 8.Link component from 0
add(Link.onClick("toHogePage", (l) -> {
  //Various processing
  setResponsePage(new toHogePage(model));
}));

//If you only move pages, it will be one line
add(Link.onClick("toHogePage", (l) -> setResponsePage(new toHogePage(model))));
//So far
Button button = new Button("toHogePage") {
  private static final long serialVersionUID = 7447947126462635005L;

  @Override
  public void onSubmit() {
    //Various processing enters around here
    setResponsePage(new HogePage(model));
  }
};

// Wicket 8.Button component from 0
Button button = Button.onSubmit("toHogePage", (b) -> {
  //Various processing enters around here
  setResponsePage(new HogePage(model));
}));

//This is also one line if only page movement
Button button = Button.onSubmit("toHogePage", (b) -> setResponsePage(new CompletionPage(orderModel)));

The arguments l and b in the lambda expression are references to the Link and button instances themselves. It can be reused in a Lambda expression in case you want to operate a Link or Button (or a Model or Form that can be obtained from it) after clicking.

** The same applies to Ajax-based Links and Buttons **. As an example, if you had a button that updates a component named wmc with Ajax when you submit a form,

//So far
AjaxButton button = new AjaxButton("button") {
  private static final long serialVersionUID = 796871957135885247L;

  @Override
  protected void onSubmit(AjaxRequestTarget target) {
    target.add(getPage().get("wmc"));
  }

  @Override
  protected void onError(AjaxRequestTarget target) {
    target.add(getForm().get("feeedback"));
  }
};

// Wicket 8.From 0, Ajax Link,Button can also be written concisely.
//The second argument Lambda expression is onSubmit,The Lambda expression of the third argument is onError.
AjaxButton button = AjaxButton.onSubmit("onSubmit",
  (b, t) -> t.add(getPage().get("wmc")),
  (b, t) -> t.add(b.getForm().get("feedback")));

It will be like. The Wicket8 Lambda expression argument b is the button itself, and the argument t is AjaxRequestTarget.

I often use these components, but they also caused mass production of anonymous classes, so I'd be very happy if I could write them in one line (at the shortest).

Behavior

Behavior, which adds behavior and functions to components with Ajax etc., also has a part that can be written simply by factory methods and lambda.

Below is an example of a Label component with Behavior that updates the component itself every second (?).

// Wicket 8.AbstractAjaxTimerBehavior from 0. The variable t is AjaxRequestTarget.
add(new Label("timer", LoadableDetachableModel.of(LocalDateTime::now))
  .setOutputMarkupId(true)
  .add(AbstractAjaxTimerBehavior.onTimer(ONE_SECOND, (t) -> t.add(getPage().get("timer")))));

In the past, it was necessary to create anonymous classes and subclasses when preparing such Behaviors. Wicket 8.0 has been devised to avoid these anonymous classes and make it easier to write with less code.

Code example

Based on the examples introduced so far, I made an example of a simple submission form (import is omitted). If you've used Wicket before, you'll find that the Java code is shorter.

Send page

<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">
<head>
  <meta charset="utf-8"/>
</head>
<body>
<h1>Order form</h1>
<form wicket:id="form">
  <dl>
    <div wicket:id="feedback"></div>
    <dt>name</dt>
    <dd><input type="text" wicket:id="userName"></dd>
    <dt>meal</dt>
    <dd>below<span wicket:id="numberOfDish"></span>Please choose one from the items.</dd>
    <dd>
      <div wicket:id="dish"></div>
    </dd>
  </dl>
  <div>
    <button type="submit" wicket:id="onSubmit">order</button>
  </div>
</form>
</body>
</html>
public class OrderPage01 extends WebPage {
  private static final long serialVersionUID = -8412714440456444538L;

  public OrderPage01() {
    IService service = new Service();
    IModel<Order> orderModel = CompoundPropertyModel.of(Model.of(new Order()));

    IModel<List<Dish>> dishes = LoadableDetachableModel.of(service::fetchDishes);

    //I'm using queue because the hierarchy is troublesome
    queue(new Form<>("form", orderModel));
    queue(new FeedbackPanel("feedback"));
    queue(new TextField<>("userName").setRequired(true));
    queue(new Label("numberOfDish", dishes.getObject()::size));
    queue(new RadioChoice<>("dish", dishes).setSuffix("<br/>").setRequired(true));
    queue(Button.onSubmit("onSubmit", (b) -> setResponsePage(new CompletionPage(orderModel))));
  }

}

Confirmation (completion) page

<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">
<head>
  <meta charset="utf-8"/>
</head>
<body>
<h1>confirmation screen</h1>
<p><span wicket:id="userName"></span>Is<span wicket:id="dish"></span>I ordered</p>
<div><a wicket:id="toIndexPage">Return</a></div>
</body>
</html>
public class CompletionPage extends WebPage {
  private static final long serialVersionUID = 3057979294465321296L;

  public CompletionPage(IModel<Order> orderModel) {
    setDefaultModel(CompoundPropertyModel.of(orderModel));
    add(new Label("userName"));
    add(new Label("dish"));
    add(Link.onClick("toIndexPage", (link) -> setResponsePage(IndexPage.class)));
  }
}

Other

How much can anonymous classes be avoided?

Unfortunately, not all Models, components, and Behaviors have a way around anonymous classes.

** ListView etc. are also typical components that are used as anonymous classes, but no particular improvement has been made, and it is necessary to use them as anonymous classes as before **.

Also, even if it is a Link or Button component, the methods that initialize the anonymous class component such as ** ʻonInitialize () and ʻonConfigure () are the same as before **. Therefore, if you want to generate a component that is set as an Ajax update target (that is, set setOutputMarkupId (boolean)) or a component that requires Validation and Visibility settings, it will continue as before. Must be anonymous class [^ 3].

[^ 3]: It is possible to connect setting methods such as setOutputMarkupId (boolean) in the form of a method chain, but the return value of these methods must be set to Component type. Yes, casting becomes indispensable when adding child components. You can use the queue well and allow the cast to proceed.

However, I think that it is comfortable to use just by simply writing Links, Buttons, and Models that are likely to be on any page that do not require such detailed control.

Unique Functional Interface

To achieve what we've introduced so far, Wicket 8.0 provides its own Functional Interface. They are WicketBiConsumer, WicketBiFunction, WicketConsumer, WicketFunction, WicketSupplier. However, these are just inheriting the Java standard Functional Interface and making it serializable.

In addition, IModel is also a FunctionalInterface, and default methods such as processing at the time of detach and default methods for intermediate processing for generating a Model from Model such as filter and map are prepared.

It seems that these can be used when creating or improving Models and components by yourself.

in conclusion

There are many other minor changes towards Wicket 7.x → 8.0, but in this article, the most interesting part of programming on a daily basis, the simplification of the description of Models and components, and I summarized the update of the part that can be avoided by anonymous class using lambda.

We hope that it will be useful for migrating products that have already been developed with Wicket, and for those who will develop with Wicket 8.0 or later in some way in the future.

A code example can be found on github.

Recommended Posts

I want to summarize Apache Wicket 8 because it is a good idea
[Java] I want to make it easier because it is troublesome to input System.out.println.
Is it possible to separate function calls and conditionals? --I want to give you a chance to write good code. 9 [C # refactoring sample]
I want to develop a web application!
I want to write a nice build.gradle
I want to write a unit test!
What is Docker? I tried to summarize
[Ruby] I want to do a method jump!
I want to simply write a repeating string
I want to design a structured exception handling
What to do when is invalid because it does not start with a'-'
I want to issue a connection when a database is created using Spring and MyBatis
7 things I want you to keep so that it doesn't become a fucking code
A memorandum because I was addicted to the setting of the Android project of IntelliJ IDEA
I want to implement it additionally while using kotlin on a site running Java
I want to call a method of another class
I want to use NetBeans on Mac → I can use it!
I want to use a little icon in Rails
I want to monitor a specific file with WatchService
I want to define a function in Rails Console
Program to determine if it is a leap year
I want to add a reference type column later
I want to click a GoogleMap pin in RSpec
I want to connect to Heroku MySQL from a client
I want to create a generic annotation for a type
I want to add a delete function to the comment function
If you want to make a Java application a Docker image, it is convenient to use jib.
[Docker] Is it good enough to call it a multi-stage build? → The story that became so good
[Java] I want to convert a byte array to a hexadecimal number
I want to find a relative path in a situation using Path
I want to implement a product information editing function ~ part1 ~
I want to make a specific model of ActiveRecord ReadOnly
I want to make a list with kotlin and java!
I want to call a method and count the number
I want to make a function with kotlin and java!
I want to create a form to select the [Rails] category
Even in Java, I want to output true with a == 1 && a == 2 && a == 3
I want to give a class name to the select attribute
I want to create a Parquet file even in Ruby
I want to use FireBase to display a timeline like Twitter
A flowing interface? -I want to give you an opportunity to write good code. 3 [C # refactoring sample]
I tried using Apache Wicket
I want to recursively search for files under a specific directory
I want to create a chat screen for the Swift chat app!
I want to make a button with a line break with link_to [Note]
I want to add a browsing function with ruby on rails
I want to use swipeback on a screen that uses XLPagerTabStrip
[Ruby] I want to put an array in a variable. I want to convert to an array
[Introduction to JSP + Servlet] I played with it for a while ♬
I tried to make Numeron which is not good in Ruby
I want to extract between character strings with a regular expression
I want to download a file on the Internet using Ruby and save it locally (with caution)