4) Installation


The following steps need to be performed to get yourself a project ready to play around with. We will go here with the classic approach of creating a war packaged maven project that is configured via a web.xml and gets tested via the tomcat7-maven-plugin. Even though we explain this here step by step, if you want to just get something to start with, just copy and modify the invesdwin-nowicket-examples-war project. As an alternative invesdwin-nowicket-examples-guide is also worth a look since it contains the code for this documentation website and is using spring-boot. Next we are going through how invesdwin-nowicket-examples-war was created. All in all you will notice that this is not much different from setting up a pure Wicket project.

The image below shows what the final directory layout looks like so you can compare while setting everything up.


Final Directory Layout

Final Directory Layout

4.1) Maven Setup

First install a current version of Maven and create a pom.xml to get the required dependencies and to be able to package and run the war project. Read the comments that are included for explanations.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>de.invesdwin</groupId>
    <artifactId>invesdwin-nowicket-examples-war</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <version.compiler>1.7</version.compiler>

        <version.maven-compiler-plugin>3.1</version.maven-compiler-plugin>
        <version.maven-processor-plugin>2.2.4</version.maven-processor-plugin>

        <version.invesdwin-nowicket>1.0.0</version.invesdwin-nowicket>
        <version.javax.servlet>3.0.0.v201112011016</version.javax.servlet>
    </properties>


    <build>
        <pluginManagement>
            <plugins>
                <!-- run this sample with "mvn tomcat7:run" and access the website at 
                    http://localhost:8080 -->
                <plugin>
                    <groupId>org.apache.tomcat.maven</groupId>
                    <artifactId>tomcat7-maven-plugin</artifactId>
                    <version>2.2</version>
                    <configuration>
                        <port>8080</port>
                        <path>/</path>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
        <plugins>
            <!-- our typical compiler configuration -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${version.maven-compiler-plugin}</version>
                <configuration>
                    <source>${version.compiler}</source>
                    <target>${version.compiler}</target>
                    <complianceLevel>${version.compiler}</complianceLevel>
                    <encoding>${file.encoding}</encoding>
                    <compilerArgument>-proc:none</compilerArgument>
                    <Xlint>ignore</Xlint>
                    <verbose>false</verbose>
                </configuration>
            </plugin>
            <!-- enable annotation processing and thus allow the invesdwin-norva constants 
                generator to create classes for our models (either annotate model with @BeanPathRoot 
                or extend AValueObject -->
            <plugin>
                <groupId>org.bsc.maven</groupId>
                <artifactId>maven-processor-plugin</artifactId>
                <version>${version.maven-processor-plugin}</version>
                <executions>
                    <execution>
                        <id>process</id>
                        <goals>
                            <goal>process</goal>
                        </goals>
                        <phase>generate-sources</phase>
                        <configuration>
                            <outputDirectory>${project.build.directory}/generated-sources/apt</outputDirectory>
                            <failOnError>false</failOnError>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>

        <!-- make development a bit easier by allowing html files and other web 
            resources to reside next to our class files so we do not have to maintain 
            two identical package trees -->
        <resources>
            <resource>
                <filtering>false</filtering>
                <directory>src/main/java</directory>
                <includes>
                    <include>**</include>
                </includes>
                <excludes>
                    <exclude>**/*.java</exclude>
                </excludes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**</include>
                </includes>
            </resource>
        </resources>
        <testResources>
            <testResource>
                <directory>${basedir}/src/test/resources</directory>
            </testResource>
            <testResource>
                <filtering>false</filtering>
                <directory>${basedir}/src/test/java</directory>
                <includes>
                    <include>**/*</include>
                </includes>
                <excludes>
                    <exclude>**/*.java</exclude>
                </excludes>
            </testResource>
        </testResources>
    </build>

    <dependencies>
        <!-- add a dependency to invesdwin-NoWicket -->
        <dependency>
            <groupId>de.invesdwin</groupId>
            <artifactId>invesdwin-nowicket</artifactId>
            <version>${version.invesdwin-nowicket}</version>
            <exclusions>
                <exclusion>
                    <!-- exclude servlet API to add special handling below -->
                    <artifactId>javax.servlet</artifactId>
                    <groupId>org.eclipse.jetty.orbit</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- for compilation we need the servlet API, but not for deployment since 
            tomcat has its own; also make sure to only have one of these jars in your 
            classpath -->
        <dependency>
            <groupId>org.eclipse.jetty.orbit</groupId>
            <artifactId>javax.servlet</artifactId>
            <version>${version.javax.servlet}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <!-- add invesdwin-oss maven repo so that NoWicket artifacts can be resolved -->
    <repositories>
        <repository>
            <id>invesdwin-oss</id>
            <url>https://invesdwin.de/repo/invesdwin-oss-remote</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>invesdwin-oss</id>
            <url>https://invesdwin.de/repo/invesdwin-oss-remote</url>
        </pluginRepository>
    </pluginRepositories>

</project>

4.2) Servlet Setup

Next create a web.xml in src/main/webapp/WEB-INF/ to register your implementation of the NoWicket servlet filter and map the error pages to nice looking ones provided by the framework (which you can customize or replace later).

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    <display-name>invesdwin-nowicket-examples-war</display-name>

    <filter>
        <filter-name>wicket</filter-name>
        <filter-class>de.invesdwin.nowicket.examples.war.ExampleWicketFilter</filter-class>
        <init-param>
            <param-name>applicationClassName</param-name>
            <param-value>de.invesdwin.nowicket.examples.war.ExampleWebApplication
            </param-value>
        </init-param>
        <init-param>
            <param-name>filterMappingUrlPattern</param-name>
            <param-value>/*</param-value>
        </init-param>
        <init-param>
            <!-- enable production error-pages -->
            <param-name>configuration</param-name>
            <param-value>deployment</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>wicket</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>ERROR</dispatcher>
    </filter-mapping>

    <error-page>
        <!-- Missing login -->
        <error-code>401</error-code>
        <location>/accessdenied</location>
    </error-page>
    <error-page>
        <!-- Forbidden directory listing -->
        <error-code>403</error-code>
        <location>/accessdenied</location>
    </error-page>
    <error-page>
        <!-- Missing resource -->
        <error-code>404</error-code>
        <location>/pagenotfound</location>
    </error-page>
    <error-page>
        <!-- Missing resource -->
        <error-code>410</error-code>
        <location>/pageexpired</location>
    </error-page>
    <error-page>
        <!-- Uncaught exception -->
        <location>/internalerror</location>
    </error-page>

</web-app>

The NoWicket servlet filter implementation is very simple. It just needs a reference to your NoWicket application config for providing the ignorePaths feature that is needed when integrating tools like Guacamole which do not play well behind a Wicket filter.

package de.invesdwin.nowicket.examples.war;

import de.invesdwin.nowicket.application.IWebApplicationConfig;
import de.invesdwin.nowicket.application.filter.AWicketFilter;

public class ExampleWicketFilter extends AWicketFilter {

    @Override
    protected IWebApplicationConfig newConfig() {
        return new ExampleWebApplicationConfig();
    }

}

The NoWicket web application implementation ia similarly simple. It also only needs a reference to the NoWicket application config and a set of base packages for which the @MountPath annotation should be scanned for.

package de.invesdwin.nowicket.examples.war;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import de.invesdwin.nowicket.application.IWebApplicationConfig;
import de.invesdwin.nowicket.application.filter.AWebApplication;
                
public class ExampleWebApplication extends AWebApplication {

    // also referenced in ExampleMarkupGenerator
    public static final Set<String> BASE_PACKAGE = Collections
            .unmodifiableSet(new HashSet<String>(Arrays.asList("de.invesdwin.nowicket.examples.war")));

    @Override
    protected IWebApplicationConfig newConfig() {
        return new ExampleWebApplicationConfig();
    }

    @Override
    public Set<String> getClasspathBasePackages() {
        return BASE_PACKAGE;
    }

}

The NoWicket application config implementation only has to specify the home page. You can optionally override more methods to do all the customizations discussed in the previous chapters.

package de.invesdwin.nowicket.examples.war;

import org.apache.wicket.markup.html.WebPage;

import de.invesdwin.nowicket.application.WebApplicationConfigSupport;
import de.invesdwin.nowicket.examples.war.page.HomePage;

public class ExampleWebApplicationConfig extends WebApplicationConfigSupport {

    @Override
    public Class<? extends WebPage> getHomePage() {
        return HomePage.class;
    }

}

4.3) Markup Generator Setup

The NoWicket markup generator implementation just needs a reference to the base packages to know where to scan for the @GeneratedMarkup annotation. You could also override more methods to change the input/output folders.

package de.invesdwin.nowicket.examples.war;

import java.util.Set;

import de.invesdwin.nowicket.generated.markup.AAnnotatedGeneratedMarkup;

public class ExampleMarkupGenerator extends AAnnotatedGeneratedMarkup {

    public static void main(final String[] args) {
        new ExampleMarkupGenerator().generate();
    }

    @Override
    protected Set<String> getClasspathBasePackages() {
        return ExampleWebApplication.BASE_PACKAGE;
    }
    
}

4.4) Home Page Setup

The home page will be demonstrated as a hello world example. For this you will need a model implementation for which the markup should be generated. Here we implement Serializable since it is the minimum required for Wicket, otherwise it is recommended to extend AValueObject.

package de.invesdwin.nowicket.examples.war.page;

import java.io.Serializable;

import de.invesdwin.nowicket.generated.markup.annotation.GeneratedMarkup;

@GeneratedMarkup
public class Home implements Serializable {

    public String getHello() {
        return "NoWicket!";
    }

}

Next we tell the markup generator to generate markup for a page implementation by writing a class for it. Here we provide our standard two constructors. We could also customize the navbar and other things that were discussed in the previous chapters, but this should better be done in an application specific base page that extends AWebPage. To keep this example as minimal as possible, we omit that here.

package de.invesdwin.nowicket.examples.war.page;

import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.wicketstuff.annotation.mount.MountPath;

import de.invesdwin.nowicket.application.AWebPage;
import de.invesdwin.nowicket.generated.binding.GeneratedBinding;

@MountPath("home")
public class HomePage extends AWebPage {

    // constructor for bookmarkable link
    public HomePage() {
        this(Model.of(new Home()));
    }

    // constructor for model redirects
    public HomePage(final IModel<Home> model) {
        super(model);
        new GeneratedBinding(this).bind();
    }

}

4.5) Running It

Now you can run your ExampleMarkupGenerator. It will produce a HomePage.html file like this:

<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">
<head></head>
<body>
    <wicket:extend>
        <form wicket:id="form">
            <!-- <div wicket:id='feedback'></div>  -->
            <div wicket:id="modal"></div>
            <div class="mb-3 row">
                <label class="col-form-label col-sm-2" wicket:for="hello"> <wicket:label>
                            Hello
                        </wicket:label></label>
                <div wicket:id="hello_gridColumn" class="col-sm-4">
                    <input type="text" class="form-control" wicket:id="hello"></input>
                </div>
            </div>
        </form>
    </wicket:extend>
</body>
</html>

And it will also generate a HomePage.properties file like this:

hello=Hello

With everything in place now, you could run your project via Maven with mvn tomcat7:run and access the website at http://localhost:8080.

4.6) Apache Server Reverse Proxy Configuration

If you want to embed/deploy your Wicket application behind an Apache Server as a Reverse Proxy (under the same context path between the java web server and apache2), you can use the following server configuration:

# enable required modules with: "a2enmod proxy proxy_html proxy_http proxy_connect substitute headers rewrite"
ProxyRequests Off
<Proxy *>
    Order deny,allow
    Allow from all
</Proxy>
ProxyPreserveHost On
RewriteEngine On

<Location /app/>
    Order allow,deny
    Allow from all
    ProxyPass http://example.com:8080/app/ flushpackets=on
    ProxyPassReverse http://example.com:8080/app/
    RewriteCond %{REQUEST_URI}  ^/app/wicket/websocket    [NC,OR]
    RewriteCond %{HTTP:UPGRADE} ^WebSocket$           [NC,OR]
    RewriteCond %{HTTP:CONNECTION} ^Upgrade$          [NC]
    RewriteRule .* ws://example.com:8080%{REQUEST_URI}  [P,QSA,L]

    RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
    RewriteRule .* http://example.com:8080%{REQUEST_URI} [P,QSA,L]

    SetOutputFilter proxy-html
    ProxyHTMLDoctype "<!DOCTYPE HTML>"
    ProxyHTMLURLMap http://example.com:8080/app/ /app/
    AddOutputFilterByType SUBSTITUTE text/html
    AddOutputFilterByType SUBSTITUTE text/css
    AddOutputFilterByType SUBSTITUTE text/js
    Substitute "s|http://example.com:8080/app/|https://example.com/app/|ni"
    RequestHeader unset Accept-Encoding
</Location>

#also put this into the default site for port 80:
RewriteEngine On
RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L]

4.7) Nginx Server Reverse Proxy Configuration

If you want to embed/deploy your Wicket application behind an Nginx as a Reverse Proxy (under the same context path between the java web server and apache2), you can use the following server configuration:

location /app/ {
    proxy_pass http://example.com:8080/app/;
    proxy_redirect http://example.com:8080/app/ https://example.com/app/;
    sub_filter "http://example.com:8080/app/" "https://example.com/app/";
    sub_filter_once off;
    sub_filter_types text/html;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header Host $host;
}

Replace example.com with your domain, 8080 with the port of your Wicket application is running on and app with the path you want to make the application available under. With this you can make the application available in a web presence at port 80 which is more firewall friendly. Also you can reuse the SSL configuration of the Apache Server without having to configure it in Java (if not, just replace https above with http). The application can then be accessed at http://example.com/app/ (or what you changed it to).