Skip to content

MarcGiffing/wicket-spring-boot

Repository files navigation

Wicket autoconfiguration with Spring Boot

Introduction

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

  1. Create a class which is marked with @SpringBootApplication - see Springs documentation

  2. 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.

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.

<dependency>
  <groupId>org.wicketstuff</groupId>
  <artifactId>wicketstuff-annotation</artifactId>
</dependency>
wicket.stuff.annotationscan.enabled=true
wicket.stuff.annotationscan.packagename=

Wicketstuff - htmlcompressor

<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

<dependency>
  <groupId>org.wicketstuff</groupId>
  <artifactId>wicketstuff-serializer-fast2</artifactId>
</dependency>
wicket.stuff.serializer.fast2.enabled=true

Wicketstuff - serializer-kryo2

<dependency>
  <groupId>org.wicketstuff</groupId>
  <artifactId>wicketstuff-serializer-kryo2</artifactId>
</dependency>
wicket.stuff.serializer.fast2.enabled=true

Wicketstuff - restannotations

<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

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

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

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

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

<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

    • Here you can optionally set configurations which only apply when deployed as a war in a Servlet Container

    • maven

    • gradle

@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)).