Wicket autoconfiguration with Spring Boot
Current release version:
-
3.1.x - Wicket 9.x with Spring Boot 2.6.1 - Branch master
-
3.0.x - Wicket 9.x with Spring Boot 2.1 - Branch master (deprecated)
-
2.1.x - Wicket 8.x with Spring Boot 2 - Branch wicket-8
-
2.0.x - Wicket 8.x with Spring Boot 1 - Branch wicket-8
-
1.x.x - Wicket 7.x with Spring Boot 1 - Branch wicket-7
Note
|
Example projects: https://github.com/MarcGiffing/wicket-spring-boot-examples |
Introduction
https://dzone.com/articles/enhance-wicket-with-spring-boot by Andrea Del Bene
This project makes it easy to create a new Wicket projects with a minimum of configuration effort. It uses Spring Boot to autoconfigure Wickets core and its extension (related projects like wicketstuff, beanvalidation…​) with an reasonable default value which can be overridden over property files.
-
Core Features
-
Configures an embedded Servlet Container by default (Spring Boot Feature).
-
Autoconfiguration of the needed Wicket Servlet filters.
-
Autoconfiguration for Spring/Spring-Security and dependency injection support with @SpringBean.
-
Autoconfiguration of Wicket Extensions.
-
Faster development support with Spring Boot - DevTools
-
Getting started
To get started you have to create a new Maven Project (or another preferred dependency/build-management tool) and add the wicket-spring-boot-starter dependency to your configuration.
<dependency>
<groupId>com.giffing.wicket.spring.boot.starter</groupId>
<artifactId>wicket-spring-boot-starter</artifactId>
</dependency>
Beside the Maven dependency configuration we need the following steps to do
-
Create a class which is marked with @SpringBootApplication - see Springs documentation
-
Create your HomePage class (with HTML) which will me marked with @WicketHomePage
@SpringBootApplication
public class WicketApplication {
public static void main(String[] args) throws Exception {
new SpringApplicationBuilder()
.sources(WicketApplication.class)
.run(args);
}
}
@WicketHomePage
public class HomePage extends WebPage {
}
<html xmlns:wicket="http://wicket.apache.org">
<head></head>
<body>
You content
<wicket:child/>
</body>
</html>
Thats all! When you execute the main method you will get a fully working and configured Wicket application. An embedded Tomcat is automatically started.
In a usal Wicket application you would like to provide custom configuration in Wickets WebApplication init() method. You could create your own 'extension' by creating a bean which implements WicketApplicationInitConfiguration
@ApplicationInitExtension
public class YouExtensionConif implements WicketApplicationInitConfiguration {
@Override
public void init(WebApplication webApplication) {
// your custom configuration
}
}
If this configuration is not enough and you want to override special methods of Wickets WebApplication class you have to create a bean which extends one of the following two classes
-
WicketBootStandardWebApplication - Without Security
-
WicketBootSecuredWebApplication - With Security - You’ll need a security provider like Spring Security
@Component
public class WicketWebApplication extends WicketBootSecuredWebApplication {
@Override
protected void init() {
super.init();
}
}
The custom WicketWebApplication is automatically picked up instead of the default provided one. You can also override the getHomePage method() if you don’t want to use the special @WicketHomePage annotations to mark the home page.
To package the application as an executable jar you have to add the spring-boot-maven-plugin.
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
Resource files (html, css, js, …​) are not copied to the target folder if placed in the src/main/java folder. You have to tell Maven to copy these files:
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**</include>
</includes>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
How does it work?
To fully understand how Spring Boots autconfiguration and in general Spring Boot works you should read the excellent documentation from this fantastic project.
As an an example we will look to the AnnotatedMountScanner configuration. The annotated mount scanner is an project which supports bookmarkable URLs configured by annotations on WebPage classes. If you have this '@MountPath("login")' annotation on a WebPage then the Page is mounted to 'http://localhost/login'.
In this project each configuration is separated in two classes to configure this particular feature/extension. The extension consists of a property and a configuration class.
The property class holds properties to configure the specific feature. In the AnnotatedMountScannerProperties class we found two properties:
@ConfigurationProperties(prefix = AnnotatedMountScannerProperties.PROPERTY_PREFIX)
public class AnnotatedMountScannerProperties {
public static final String PROPERTY_PREFIX = "wicket.stuff.annotationscan";
/**
* @see AnnotatedMountScannerConfig
*/
private boolean enabled = true;
/**
* An alternative package name for scanning for mount path if the
* WicketApplication should not used as the root scan package
*/
private String packagename;
This property file can be imported in the configuration class AnnotatedMountScannerConfig.
/**
* Auto configuration for the {@link AnnotatedMountScanner}.
*
* It uses the user defined {@link WebApplication} as the default package scan
* root directory.
*
* Enables annotate mount scanner if the following two condition matches:
*
* 1. The {@link AnnotatedMountScanner} is in the classpath.
*
* 2. The property {@link AnnotatedMountScannerProperties#PROPERTY_PREFIX}
* .enabled is true (default = true)
*
*
* @author Marc Giffing
*
*/
@ApplicationInitExtension
@ConditionalOnProperty(prefix = AnnotatedMountScannerProperties.PROPERTY_PREFIX, value = "enabled", matchIfMissing = true)
@ConditionalOnClass(value = org.wicketstuff.annotation.scan.AnnotatedMountScanner.class)
@EnableConfigurationProperties({ AnnotatedMountScannerProperties.class })
public class AnnotatedMountScannerConfig implements WicketApplicationInitConfiguration {
@Autowired
private AnnotatedMountScannerProperties prop;
@Override
public void init(WebApplication webApplication) {
String packagename = webApplication.getClass().getPackage().getName();
if (prop.getPackagename() != null) {
packagename = prop.getPackagename();
}
new AnnotatedMountScanner().scanPackage(packagename).mount(webApplication);
}
}
If all conditions on the AnnotatedMountScannerConfig matches the configuration class is configured as a spring bean. All Spring beans which implements the interface WicketApplicationInitConfiguration will be injected as a list in the default WicketBootWebApplication class.
In the WicketBootWebApplication class we iterate in Wickets init method over the list and call on each the init method to configure the application.
public class WicketBootWebApplication extends AuthenticatedWebApplication {
@Autowired(required = false)
private List<WicketApplicationInitConfiguration> configurations = new ArrayList<>();
@Override
protected void init() {
super.init();
for (WicketApplicationInitConfiguration configuration : configurations) {
configuration.init(this);
}
}
}
Spring profile configuration
The Wicket Spring Boot Starter project ships with a default development configuration. It can be activated by activating the 'development' Spring profile in the main class or over external JVM/Maven arguments.
The default configuration can be overridden with a custom property file. See Spring Boots reference documentation here.
wicket:
core:
settings:
general:
configuration-type: development
debug:
enabled: true
component-use-check: true
development-utilities-enabled: true
stuff:
htmlcompressor:
enabled: false
features:
removeComments: false
removeMultiSpaces: false
removeIntertagSpaces: false
removeQuotes: false
compressJavaScript: false
compressCss: false
simpleDoctype: false
removeScriptAttributes: false
removeStyleAttributes: false
removeLinkAttributes: false
removeFormAttributes: false
removeInputAttributes: false
simpleBooleanAttributes: false
removeJavaScriptProtocol: false
removeHttpProtocol: false
removeHttpsProtocol: false
preserveLineBreaks: false
external:
development:
devutils:
statelesschecker:
enabled: true
interceptor:
enable-live-sessions-page: true
diskstorebrowser:
enabled: true
wicketsource:
enabled: true
Custom conditions
This section lists custom conditional configuration like Spring Boot ones.
@ConditionalOnWicket
With the ConditionOnWicket annotation you can check that configuration classes only apply on a specific Wicket major version. If some functionality is only available on Wicket 7 you can use this annotation.
@ApplicationInitExtension
@ConditionalOnWicket(value=7, range=Range.EQUALS_OR_HIGHER)
public ConditionalConfig implements WicketApplicationInitConfiguration{
@Override
public void init(WebApplication webApplication) {
// configuration option which only apply to Wickets major version 7 or higher
}
}
Extensions
The following section describes the current extensions and the required dependencies. An extension is a custom labeling in this project which is used to auto-configure a specific part of an Wicket application. An extension may require an external dependency or is using Wickets core features. See section How does it work? to get a deeper knowledge.
-
-
DEPLOYMENT-CONFIGURATIONS
-
DEVELOPMENT-CONFIGURATIONS
-
General
Wicket can be started in DEVELOPMENT and DEPLOYMENT mode. You can change the configuration type over the following property configuration. The given property is automatically mapped to Wickets ConfigurationType enumeration.
wicket.core.settings.general.configuration-type=development # development/deployment(default)
wicket.web.servlet.filter-mapping-param=/*
wicket.web.servlet.dispatcher-types=request, error, async # request, error, async, include, forward
wicket.web.servlet.init-parameters.*= # map<string,string> - configuration support for additional servlet init parameters
#exception settings
wicket.core.settings.exceptions.thread-dump-strategy=thread_holding_lock
wicket.core.settings.exceptions.error-handling-strategy-during-ajax-requests=redirect_to_error_page
wicket.core.settings.requestcycle.render-strategy=redirect-to-buffer # redirect-to-buffer / one-pass-render / redirect-to-render
wicket.core.settings.requestcycle.buffer-response=true
wicket.core.settings.requestcycle.gather-extended-browser-info=false
wicket.core.settings.requestcycle.response-request-encoding=UTF-8
wicket.core.settings.requestcycle.timeout-size=1
wicket.core.settings.requestcycle.timeout-unit=minutes
wicket.core.settings.requestcycle.exception-retry-count=10
#Markup - Settings
wicket.core.settings.markup.default-markup-encoding=UTF-8 # if null it uses the system default
wicket.core.settings.markup.automatic-linking=false
wicket.core.settings.markup.compress-whitespace=false
wicket.core.settings.markup.strip-comments=false
wicket.core.settings.markup.strip-wicket-tags=true
wicket.core.settings.markup.throw-exception-on-missing-xml-declaration=false
#RequestLogger - Settings
wicket.core.settings.requestlogger.enabled=false
wicket.core.settings.requestlogger.record-session-size
wicket.core.settings.requestlogger.requests-window-size
wicket.core.requestmapper.cryptmapper.enabled=false # URL encryption support
wicket.core.settings.pagestore.enabled=false # enables custom store settings
wicket.core.settings.pagestore.session-size=2
wicket.core.settings.pagestore.session-unit=megabytes
wicket.core.settings.pagestore.asynchronous= # overrides wickets default value only when set
wicket.core.settings.pagestore.asynchronous-queue-capacity= # overrides wickets default value only when set
wicket.core.settings.pagestore.file-store-folder= # overrides wickets default value only when set
wicket.core.settings.pagestore.inmemory-cache-size= # overrides wickets default value only when set
If you insert e.g. developmentx you will get a startup error:
Field error in object 'wicket' on field 'configurationType': rejected value [developmentx]; codes [typeMismatch.wicket.configurationType
Special Annotations
-
@WicketHomePage
-
A Page marked with this annotation will be configured as the default home page. If multiple WicketHomePage annotation found an exception is thrown.
-
-
@WicketSignInPage
-
A Page marked with this annotation will be configured as the default login page. A security provider like Spring Security is needed. If multiple annotations found an exception is thrown.
-
-
@WicketAccessDeniedPage
-
A Page marked with this annotation will be configured as the default access denied page.
-
-
@WicketInternalErrorPage
-
A Page marked with this annotation will be configured as the default internal error page.
-
-
@WicketExpiredPage
-
A Page marked with this annotation will be configured as the default expired page.
-
Spring Security
This starter detects automatically Spring Security if the Spring Boot Starter Security dependency is added. Internally the WicketBootSecuredWebApplication is used instead of the WicketBootStandardWebApplication class.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
If you wan’t to disable the Spring Security configuration for Wicket use the following property.
wicket.external.spring.security=false
Wicket Native WebSockets
This project provides an auto configuration support for native WebSockets. If the required dependencies are in the classpath a JavaxWebSocketFilter servlet filter is configured instead of the default WicketFilter.
To simplify the usage of sending WebSocket messages a class named WebSocketMessageBroadcaster is automatically registered as a spring bean. You can inject the class anywhere with @SpringBean and use the 'send' method to send WebSocket messages.
wicket.external.websocket=true # enables WebSocket support - dependency required (move documentation to seperated section)
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-native-websocket-javax</artifactId>
</dependency>
Bean validation
Wicket support for JSR 303 Bean validation. See Wickets user guide Validation with JSR 303
To enable Wickets bean validation you have to add the wicket-bean-validation dependency to your project. It will automatically configured and can be used in the project.
wicket.external.beanvalidation.enabled=true # enabled by default if bean validation project is present
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-bean-validation</artifactId>
</dependency>
Core - Prevention of CSRF Attacks
wicket.core.csrf.enabled=true
wicket.core.csrf.no-origin-action=allow
wicket.core.csrf.conflicting-origin-action=abort
wicket.core.csrf.error-code=400
wicket.core.csrf.error-message=Origin does not correspond to request
wicket.core.csrf.accepted-origins[0]=domain.name.tld # Just the domain name, no protocol
wicket.core.csrf.accepted-origins[1]=other-domain.name.tld # Add more origins by increasing the index
#TODO: There are some configuration options which should be added
Webjars
wicket.external.webjars.enabled=true
<dependency>
<groupId>de.agilecoders.wicket.webjars</groupId>
<artifactId>wicket-webjars</artifactId>
</dependency>
Wicketstuff - annotationscan
Use wicketstuff-annotation to use Java Annotations and class path searching to mount your Wicket pages.
See documentation
<dependency>
<groupId>org.wicketstuff</groupId>
<artifactId>wicketstuff-annotation</artifactId>
</dependency>
wicket.stuff.annotationscan.enabled=true
wicket.stuff.annotationscan.packagename=
Wicketstuff - htmlcompressor
See documentation
<dependency>
<groupId>org.wicketstuff</groupId>
<artifactId>wicketstuff-htmlcompressor</artifactId>
</dependency>
<dependency>
<groupId>com.yahoo.platform.yui</groupId>
<artifactId>yuicompressor</artifactId>
</dependency>
wicket.stuff.htmlcompressor.enabled=true
wicket.stuff.htmlcompressor.features.*=
Core - serializer-deflated
wicket.core.serializer.deflated.enabled=false # has to be explicit enabled. deflates the outputstream, reducing page store size by up to a factor 8 at a price of about 2-20ms
Wicketstuff - serializer-fast2
See documentation
<dependency>
<groupId>org.wicketstuff</groupId>
<artifactId>wicketstuff-serializer-fast2</artifactId>
</dependency>
wicket.stuff.serializer.fast2.enabled=true
Wicketstuff - serializer-kryo2
See documentation
<dependency>
<groupId>org.wicketstuff</groupId>
<artifactId>wicketstuff-serializer-kryo2</artifactId>
</dependency>
wicket.stuff.serializer.fast2.enabled=true
Wicketstuff - restannotations
See documentation
<dependency>
<groupId>org.wicketstuff</groupId>
<artifactId>wicketstuff-restannotations</artifactId>
</dependency>
<dependency>
<groupId>org.wicketstuff</groupId>
<artifactId>wicketstuff-restannotations-json</artifactId>
</dependency>
wicket.stuff.restannotations.enabled=true
wicket.stuff.restannotations.packagename= # the package name to scan for project specific annotations
General - debugsettings
Wicket provides some debug settings which could be
wicket.core.settings.debug.enabled=false
wicket.core.settings.debug.developmentUtilitiesEnabled=true # Enables all of the panels and pages, etc, from wicket-devutils package.
wicket.core.settings.debug.ajaxDebugModeEnabled=true # if true: wicket-ajax-debug.js is added to header
wicket.core.settings.debug.componentUseCheck=true
wicket.core.settings.debug.outputMarkupContainerClassName=false
wicket.core.settings.debug.componentPathAttributeName=
Datastore
Datastore HttpSession
wicket.core.datastore.httpsession.enabled=false
wicket.core.datastore.httpsession.pagesNumber=20 # the maximum number of pages the data store can hold
Datastore cassandra
See Documentation
wicket.stuff.datastore.cassandra.enabled=true
wicket.stuff.datastore.cassandra.contact-points= #comma-separated list
wicket.stuff.datastore.cassandra.table-name=pagestore
wicket.stuff.datastore.cassandra.keyspace-name=wicket
wicket.stuff.datastore.cassandra.record-ttl=30
wicket.stuff.datastore.cassandra.record-ttl-unit=minutes
wicket.stuff.datastore.cassandra.session-size=2
wicket.stuff.datastore.cassandra.session-unit=megabytes
<dependency>
<groupId>org.wicketstuff</groupId>
<artifactId>wicketstuff-datastore-cassandra</artifactId>
</dependency>
Datastore hazelcast
See Documentation
wicket.stuff.datastore.hazelcast.enabled=true
wicket.stuff.datastore.hazelcast.session-size=2L
wicket.stuff.datastore.hazelcast.session-unit=megabytes
<dependency>
<groupId>org.wicketstuff</groupId>
<artifactId>wicketstuff-datastore-hazelcast</artifactId>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
</dependency>
Datastore memcached
See Documentation
wicket.stuff.datastore.memcached.enabled=true
wicket.stuff.datastore.memcached.session-size=2L
wicket.stuff.datastore.memcached.session-unit=megabytes
wicket.stuff.datastore.memcached.expiration-time=30
wicket.stuff.datastore.memcached.port=11211
wicket.stuff.datastore.memcached.server-names=
wicket.stuff.datastore.memcached.shutdown-timeout=30
wicket.stuff.datastore.memcached.shutdown-timeout-unit=minutes
<dependency>
<groupId>org.wicketstuff</groupId>
<artifactId>wicketstuff-datastore-memcached</artifactId>
</dependency>
Datastore redis
See Documentation
wicket.stuff.datastore.redis.enabled=true
wicket.stuff.datastore.redis.session-size=2L
wicket.stuff.datastore.redis.session-unit=megabytes
wicket.stuff.datastore.redis.expiration-time=30
wicket.stuff.datastore.redis.port=11211
wicket.stuff.datastore.redis.server-names=
wicket.stuff.datastore.redis.shutdown-timeout=30
wicket.stuff.datastore.redis.shutdown-timeout-unit=minutes
<dependency>
<groupId>org.wicketstuff</groupId>
<artifactId>wicketstuff-datastore-redis</artifactId>
</dependency>
Wicketstuff - JAMon
Used to monitor page requests. Provides a statistic page.
See Github
wicket.stuff.monitoring.jamon.enabled=true
wicket.stuff.monitoring.jamon.include-source-name-in-monitor-label=true
wicket.stuff.monitoring.jamon.mountPage=/monitoring/jamon # the url to which the statistic page is mounted
<dependency>
<groupId>org.wicketstuff</groupId>
<artifactId>wicketstuff-jamon</artifactId>
</dependency>
Note
|
JAMon includes hazelcast to gather statistics. You may need to disable the datastore hazelcast support: Datastore hazelcast |
Devutils
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-devutils</artifactId>
</dependency>
Devutils - diskstorebrowser
wicket.external.development.devutils.diskstorebrowser.enabled=false
wicket.external.development.devutils.diskstorebrowser.mountPage=devutils/diskstore/browser
Devutils - inspector
wicket.external.development.devutils.diskstorebrowser.enabled=false
wicket.external.development.devutils.diskstorebrowser.mountPage=devutils/diskstore/browser
Devutils - statelesschecker
wicket.external.development.devutils.interceptor.enableLiveSessionsPage=false
wicket.external.development.devutils.interceptor.liveSessionPageMount=devutils/inspector/live-session-page
Wicket-Source
See documentation
<dependency>
<groupId>com.github.jennybrown8.wicket-source</groupId>
<artifactId>wicket-source</artifactId>
</dependency>
wicket.external.development.wicketsource.enabled=false
Spring Boot - DevTools
The project tries to improve the development-time experience when working with Spring Boot. There is a problem with Wickets default and other serializer (fast2, kryo2…​). See Issue 29 If the spring-boot-devtools dependency is in the classpath a special Spring serializer will be activated.
All other serializer will only be activated if the Spring Boot DevTools dependency is not in the classpath.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
Configure as WAR for deploy on Servlet Container
#115
To run the application as a war file in the Servlet Container like Tomcat you have to do the following steps.
-
Set the packaging to war in your build system (maven, gradle)
-
Mark the spring-boot-starter-tomcat dependency as provided
-
Use the Spring provided plugins to repackage the project
-
Extend from SpringBootServletInitializer
@SpringBootApplication
public class WicketApplication extends SpringBootServletInitializer {
//Can be used while developing
public static void main(String[] args) throws Exception {
new SpringApplicationBuilder()
.sources(WicketApplication.class)
.run(args);
}
//Executed when deployed as a WAR in a Servlet container.
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
builder.properties( WebSocketWicketWebInitializerAutoConfiguration.REGISTER_SERVER_ENDPOINT_ENABLED + "=false" );
return super.configure( builder );
}
}
If you already extend from a Wicket specific class you can create a separated class which extends from SpringBootServletInitializer (#115 (comment)).