List of major changes from 1.2 to 2.0 (under development)
See the changelist for minor changes and bug fixes.
IMPORTANT - SILENT FAILURES
Application.get() and Session.get() will return null when threadlocals are not set instead of throwing an NPE
JSE 5
Wicket 2.0 requires Java 5. The main reason behind making this change is that we felt Wicket 1.2 is good enough to last a long time, and we didn't want to miss out any longer on parameterized types (generics).
Attach/detach listeners refactor
onAttach() and onDetach() overrides must now call their super implementation, otherwise an IllegalStateException is thrown.
It is recommended that onAttach() overrides call super implementation first, and onDetach() last.
Generics/ Parameterized types
Wicket re-implemented models and components to take advantage of generics.
for instance:
ListView numbers = new ListView<String>(group, "numbers", NUMBERS) { @Override protected void populateItem(ListItem<String> item) { new Radio<String>(item, "radio", item.getModel()); new Label(item, "number", item.getModelObject()); }; };
list views will now only accept models that produce lists. The above example it is a list with Strings (declared like <List<String>>). Also, item.getModelObject produces a string directly.
The increased type safety allows you make your client code more robust, and it also allows you to develop components that have a 'tighter' interface to the outside world. E.g. you can now specify that a component only works with models that produce Person objects.
There are a couple of minor enhancements we did or plan to do, like using enums instead of wicket.util.lang.EnumeratedType (which theoretically might have serialization issues) and enhanced for loops etc.
Constructor change
Component#add is removed in favor of passing in the parent using the component's constructor. So instead of using Component#add to build the component hierarchy, you need to pass in the proper parents to reflect the hierarchy. This change is usually referred to as the 'constructor change' in discussions on the mailing lists and the IRC channel.
Instead of:
MyLink link = new MyLink("link"); add(link); link.add(new Label("myLabel", "myText"));
you now do:
MyLink link = new MyLink(this, "link"); new Label(link, "myLabel", "myText");
The greatest advantage of passing in parents in the constructor instead of having method add, is that the full component hierarchy is known at construction time. This allows Wicket to know the exact coupling of the component to markup elements. It is very convenient for 'rich' components (you typically need to know the path/ unique id of a component when you work with javascript) but also allows you to do things like directly manipulating the tag's attributes (attribute modifiers won't be needed as much) and allows Wicket to fail early and with better information if the component hierarchy does not match the hierarchy as declared in the markup file(s).
Replacing components
Before the constructor change, you would call Component#replace to replace a component with another one. This method does not exist anymore. Instead, you either create a new component with the same parent and id (so the hierarchy will match; the new component is then the current), or you call Component#reAttach to set it as the current one.
If you look at
wicket.examples.template.TemplatePage
, in 1.2, the code to replace a banner looked like this:
add(new Link("changeAdLink") { public void onClick() { if (currentBanner.getClass() == Banner1.class) { TemplatePage.this.replace(currentBanner = new Banner2("ad")); } else { TemplatePage.this.replace(currentBanner = new Banner1("ad")); } } });
After the constructor change, the code with the same effect, looks like this:
new Link(this, "changeAdLink") { public void onClick() { if (currentBanner.getClass() == Banner1.class) { new Banner2(TemplatePage.this, "ad"); } else { new Banner1(TemplatePage.this, "ad"); } } };
Alternatively, this could be rewritten like:
new Link(this, "changeAdLink") { public void onClick() { if (currentBanner == banner1) { currentBanner = banner2; } else { currentBanner = banner1; } currentBanner.reAttach(); } };
Where we would hold references to banner1 and banner2 in the page after we created them:
banner2 = new Banner2(this, "ad"); currentBanner = banner1 = new Banner1(this, "ad");
Note that as we created banner1 after banner2 here, banner1 will be the current one.
Replaced EnumeratedType by JSE 5's enum
The enums that were previously instances of EnumeratedType are accessed slightly different. That is because the enum elements are part of the enum definition; they are not declared outside of it like was the case with the EnumeratedTypes. For instance, instead of:
IRequestCycleSettings.ONE_PASS_RENDER
you now have to access that same element as:
IRequestCycleSettings.RenderStrategy.ONE_PASS_RENDER
or
import wicket.settings.IRequestCycleSettings.RenderStrategy; ... RenderStrategy.ONE_PASS_RENDER
Opened up for covariance
Covariance, which comes with JDK 5, triggered us to remove final from some methods where covariance typically is nice to use, like Component.getSession and Component.getApplication. Thus, you can now define
@Override public LibrarySession getSession() { return (LibrarySession)super.getSession(); }
in a base class and then use
LibrarySession libSession = getSession();
Or - probably even more useful:
public final class LibrarySession extends WebSession { public static LibrarySession get() { return (LibrarySession)Session.get(); } ...
and
LibrarySession.get().isSignedIn();
Filter instead of a Servlet
Wicket no longer uses a Servlet as it's main option. In fact, using a servlet filter (WicketFilter) will be
the recommended pattern. Replace code like (in subclasses of WebApplication):
ServletContext sc = getWicketServlet().getServletContext();
with
ServletContext sc = getServletContext();
and
wicket.protocol.http.IWebApplicationFactory#createApplication(wicket.protocol.http.WicketServlet)
is replaced by
wicket.protocol.http.IWebApplicationFactory#createApplication(wicket.protocol.http.WicketFilter)
You can get the servlet context from a filter like this:
filter.getFilterConfig().getServletContext()
The main advantage of working with a filter instead of a servlet is that it is easier to pass through resources, and map your application to the root.
Here's an example of how to configure your application now with a filter in web.xml:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <filter> <filter-name>MyApplication</filter-name> <filter-class>wicket.protocol.http.WicketFilter</filter-class> <init-param> <param-name>applicationClassName</param-name> <param-value>com.myapp.MyApplication</param-value> </init-param> <init-param> <param-name>filterPath</param-name> <param-value>app</param-value> </init-param> </filter> <filter-mapping> <filter-name>MyApplication</filter-name> <url-pattern>/app/*</url-pattern> </filter-mapping> </web-app>
Wicket-Spring
@SpringBean.name has been deprecated and replaced with @SpringBean.id which aligns much better with spring.
Validation Changes
Form component level validation has been decoupled from FormComponent so that validators can be reused outside wicket. The new API can be found in wicket.validation
package, with the validator implementations in wicket.validation.validator
. From the point of view of validator development not much has changed if you extended the AbstractValidator
; if you however implemented the IValidator
interface directly you will need to use the new API, namely error reporting via ValidationError
instead of FormComponent.error(List,Map)
. Errors with messages fully constructed inside the validator can still be reported using FormComponent.error(String)
.
XML resource bundles
In addition to the .properties format for localized messages, Wicket now supports Java 5's XML format for messages. Like with normal messages, you can just put them next to your component class with the proper locale in the name etc. For instance: MyPanel_nl.xml would be the Dutch language bundle next to MyPanel.class. The format is described here http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html and here http://www-128.ibm.com/developerworks/java/library/j-tiger02254.html?ca=dgr-SEOn-JDK_5.0-XML. The greatest advantage is that you can declare which encoding should be used, thus enabling you to directly maintain your bundles for non-ASCII sets (with normal properties, you have to use escape codes for non-ASCII characters).
ISessionStore
ISessionStore had the following changes:
String getSessionId(Request request); -> String getSessionId(Request request, boolean create);
+ void onBeginRequest(Request request);
+ void onEndRequest(Request request);
+ PageMap createPageMap(String name, Session session);
By default, the creation of lasting sessions is deferred until actually needed. As long no lasting session is yet created and users are accessing stateless pages, a temporary session object is used for the current request.
Misc API breaks
Model hierarchy cleanup
Some miscellaneous abstract model classes have been removed as they did not provide much value. Here is a simple migration mapping if you extended one of the removed classes in your code:
AbstractModel->Model
AbstractDetachableModel->LoadableDetachableModel
DatePicker
the DatePicker component has been removed from the wicket-extensions package. It now lives as a separate project on http://wicket-stuff.sf.net/wicket-contrib-datepicker. If you require the datepicker to be present in your code, and don't want to use the new date picker component, then add the following depenency to your maven project (or download the distribution from sourceforge):
<dependency> <groupId>wicket-stuff</groupId> <artifactId>wicket-contrib-datepicker</artifactId> <version>1.2</version> </dependency>
ISessionStore
ISessionStore had the following changes:
String getSessionId(Request request); -> String getSessionId(Request request, boolean create);
+ void onBeginRequest(Request request);
+ void onEndRequest(Request request);
+ PageMap createPageMap(String name, Session session);
By default, the creation of lasting sessions is deferred until actually needed. As long no lasting session is yet created and users are accessing stateless pages, a temporary session object is used for the current request.
Button
AjaxSubmitButton and AjaxSubmitLink now extend Button and Button extends IFormSubmittingComponent. As a result of this, method onSubmit changed from protected to public
IHeaderContributor
void renderHead(final Response response); -> void renderHead(final IHeaderResponse response);
This resulted in a couple of cascading changes, like methods onRenderHeadContribution and onRenderHeadInitContribution not being used anymore. Note that the filtering of duplicate contributions is now part of IHeaderResponse.
A common fix is this:
protected void onRenderHeadInitContribution(Response response) { writeJsReference(response, AUTOCOMPLETE_JS); }
should be converted to:
public void renderHead(IHeaderResponse response) { super.renderHead(response); response.renderJavascriptReference(AUTOCOMPLETE_JS); }
or for instance code like
protected String getImplementationId() { return "ArchiveActions"; } protected void onRenderHeadContribution(Response response) { if (!isComplete()) { response.write("<script>"); response.write(getCallbackScript().toString()); response.write("</script>"); } }
would be rewritten like
public void renderHead(IHeaderResponse response) { if (!isComplete()) { response.renderJavascript(getCallbackScript(), "ArchiveActions"); } }
ISessionFactory
Session newSession() -> Session newSession(Request)
FormComponent.IVisitor
Things have been refactored into interfaces here. If you previously implemented this directly, extend FormComponent.AbstractVisitor instead, and rename your formComponent() implementation to onFormComponent().
Logging API Change
The Logging API changed from Commons Logging to SLF4J, so you need to update your POM in order to use it:
e.g:
*if you are using commons-logging:
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jcl</artifactId> <version>1.1.0</version> </dependency>
*if you are using log4j
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.1.0</version> </dependency>
ClientProperties
wicket.protocol.http.ClientProperties was taken from the Echo2 project that uses a license which is incompatible with ASL2. It has therefore been rewritten and the usage of it has also changed.
Instead of:
WebClientInfo clientInfo = (WebClientInfo) Session.get().getClientInfo(); ClientProperties properties = clientInfo.getProperties(); // Before constants where used to get properties System.out.println(properties.get(ClientProperties.BROWSER_INTERNET_EXPLORER));
You now say:
WebClientInfo clientInfo = (WebClientInfo) Session.get().getClientInfo(); ClientProperties properties = clientInfo.getProperties(); // Now you use a property with that name instead System.out.println(properties.isBrowserInternetExplorer());
Custom Sessions
Session's constructor signature is now:
protected Session(Application application, Request request)
The locale is not set right after construction in WebApplication, but rather in the constructor using the passed in request. You can now 'fix' the session's locale by setting it in it's constructor.
MetaDataKey
MetaDataKey, now uses generics rather than a class argument to ensure typing. So, in Wicket 1.2 you did:
new MetaDataKey(ActionPermissions.class) { };
but in Wicket 2.0, you do:
new MetaDataKey<ActionPermissions>() { };
IResourceFinder
The interface has been slightly changed. A class parameter has been added to find() and the value returned must now be an IResourceStream instead of an URL.
ResourceStreamLocator
IResourceStreamLocator and all its implementations are gone and have been replaced by a single IResourceStreamFactory and its default implementation ResourceStreamFactory. In order to change or extend the default behaviour you may subclass the default implementation and register it with the application (resource settings) as usual. OsgiResourceStreamFactory (core) and CustomResourceStreamFactory (examples) are two examples which show how do it.
Note that resource finder (IResourceFinder implementations) are still being used. This detail has not been changed.
H4. Custom resource loading
In Wicket 1.2 you could override method newMarkupResourceStream from MarkupContainer to provide a custom resource stream for loading the component's markup. The new way of letting markup containers provide custom markup is to let them implement interface IMarkupResourceStreamProvider and implement it's method getMarkupResourceStream. Additionally, a new feature is that you can provide your own markup cache key, which is used in the MarkupCache class. The only real use case for that is to let a markup container return a cache key that is null, in which case the resource won't be cached, causing Wicket to get call getMarkupResourceStream everytime the component's markup is requested. A use case for that is when you have dynamic markup (e.g. from a database) that is request/ session dependent. To achieve this, let your markup container also implement IMarkupCacheKeyProvider and let method getCacheKey return null.
Test serialization setting removed
The setting IDebugSettings#SerializeSessionAttributes is removed. Instead you can use the SecondLevelCacheSessionStore, which serialized (old) pages to a second level cache (user.tmp by default). SecondLevelCacheSessionStore will (probably) be the default configured session store, but you can configure it yourself by doing in your application:
/** * @see wicket.Application#newSessionStore() */ protected ISessionStore newSessionStore() { return new SecondLevelCacheSessionStore(new FilePageStore()); }
Also, you can use
wicket.util.Objects#checkSerializable(Object)
to check for non-serializable objects yourself. You could do this for instance in a custom session store implementation.