This is an old revision of the document!


New Java Web Applications

End of life sometime after 2019. Life will be prolonged by major and minor updates to ALL frameworks and libraries
(by progressively updating the app whenever a new version comes out for libraries we rely on, we can prevent spending days/weeks/months on a rewrite in the future)

If you are making a company project :

  • You NEED nexus access, contact anthony.arents@mentoringsystems.be

All new projects have specific requirements in the buildprocess :

  • Netbeans project
  • Maven (download latest version for significant speed/memory improvements)
  • Spring (latest)
  • Spring Security (latest)
  • Hibernate (latest 5.4)
  • Javamelody (latest, developer tool, used for statistics)
  • ExtJS (frontend latest) or Bootstrap+JQuery
  • Java 8 or 9, it's compatible with both
  • Mysql 5.7 (beware the date madness : timestamp(6) & datetime(6))
  • Tomcat 8.5 (DOWNLOAD NEWEST FROM THEIR WEBSITE! required for java config, earlier versions supplied with netbeans are bad)

Code examples were taken from BoardPortal, PayByMail, Merke, SuiteOfTheFuture, Mobi
This documentation is still a work in progress.

Tools

  • Netbeans 8.2
  • Mysql 5.7 : you can use the installer, copy the password when presented ! Mac users : you can find the start & stop in System Preferences : use sequel pro to connect)

To remove from existing applications

Things that SHOULD BE dropped and no longer used :

  • SimpleDateFormat (use FastDateFormat)
  • Base64 encoding (unless it's needed for webservices, then we'll be switching to java8 base64 utils)
  • iText (would be better to switch to apache pdfbox… unless a license is available)
  • javacsv (can be done with jackson library now)
  • Apache httpclient (can probably be easily replaced by something easier to use)
  • commons fileupload (no longer needed)
  • Dozer (wish we never used this slow thing, got beaten by Mario for a reason)
  • Joda time (obsolete with java8)

Tracking versions

Versions plugin

mvn site → generated version reports

Spring Blog

Thread safety is important

Be aware, sometimes, a method is not safe for use in webapplications, prevent setting things static at all time
unless they are threadsafe (like Log4J).
For example : SimpleDateFormat, when made static is thread unsafe & may start confusing dates passed to it → use new JAVA8 date.

Maven site usage

Please use the maven site ! It will calculate PMD + Findbugs for you.
Just rightclick the project : “Run Maven” → “Goals …” → type “site” & save as “site”.
You'll be able to run it again with “Run Maven” → “site”.

In the generated output, there's a link : “View generated site”. You can use this link when Maven is finished.

You should solve all Findbugs & PMD errors. (except maybe jaxws & hibernate packages)

Package structure :

be.mentoringsystems.applicationname.config

Contains Configuration classes used to configure the application

be.mentoringsystems.applicationname.config.initializer

Contains 2 classes: one to create the servlet and one to initialize spring security

be.mentoringsystems.applicationname.dao

Contains DAO interfaces, naming ObjectnameDAO

be.mentoringsystems.applicationname.dao.hibernate

Contains DAO implementations, naming ObjectnameDAOImpl, extends HibernateDaoSupport, implements ObjectnameDAO

be.mentoringsystems.applicationname.dao.importer

Contains importer interfaces

be.mentoringsystems.applicationname.dao.importer.excel

Contains excel importer interfaces

be.mentoringsystems.applicationname.dao.importer.csv

Contains csv importer interfaces

be.mentoringsystems.applicationname.model

Contains helper models which may be used throughout the application (for example QueryParams)

be.mentoringsystems.applicationname.model.db

Contains models which will be stored in the database, naming Ojectname

be.mentoringsystems.applicationname.model.dto

Contains models which will be intermediaries for the Controller interfaces (forms sent to server end up in a dto which are then translated to models in the service api)

be.mentoringsystems.applicationname.presentation

Contains viewcontrollers, Restcontrollers have their own package to make it easier to find them.

be.mentoringsystems.applicationname.presentation.conversion

Contains convertors for deserializing (spring).

be.mentoringsystems.applicationname.presentation.excel

Contains excel views using apache poi

be.mentoringsystems.applicationname.presentation.rest

Contains controllers which will be accessed via Rest

be.mentoringsystems.applicationname.presentation.serializer

Contains Jackson serializers

be.mentoringsystems.applicationname.service

Contains Service interfaces, naming ObjectnameService

be.mentoringsystems.applicationname.service.impl

Contains Service implementations, naming ObjectnameServiceImpl, implements ObjectnameService, uses ObjectnameDAO & Transactional annotations

be.mentoringsystems.applicationname.tasks

Contains ScheduledTasks class which has several automated tasks

German Projects

de.jcpis.applicationname

see above for details.

How to start :

Start with creating mocks & database model

Create a new project : New Project → Maven → Web Application

POM :

Change the POM (configuration files → pom.xml) to this :

pom.xml
<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>be.mentoringsystems</groupId>
    <artifactId>Merke</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
 
    <name>Merke</name>
 
    <properties>
        <!-- tomcat7 -->
        <!-- javax.servletapi>3.1.0</javax.servletapi -->
        <!-- javax.servletjspapi>2.3.1</javax.servletjspapi -->
        <!-- javax.elapi>3.0.0</javax.elapi -->
        <!-- tomcat8 -->
        <javax.servletapi>4.0.1</javax.servletapi>
        <javax.servletjspapi>2.3.3</javax.servletjspapi>
        <javax.elapi>3.0.0</javax.elapi>
 
        <spring>5.2.0.RELEASE</spring>
        <springsecurity>5.2.0.RELEASE</springsecurity>
        <mysql>8.0.17</mysql>
        <dbcp>2.7.0</dbcp>
        <hibernate>5.4.6.Final</hibernate>
        <hibernate.validator>6.0.17.Final</hibernate.validator>
        <jackson>2.10.0</jackson>
        <apache.taglibs>1.2.5</apache.taglibs>
        <log4j>2.12.1</log4j>
        <commons.lang>3.9</commons.lang>
        <commons.text>1.8</commons.text>
        <commons.io>2.6</commons.io>
        <commons.beanutils>1.9.4</commons.beanutils>
        <javax.mail>1.6.2</javax.mail>
        <apache.poi>4.1.0</apache.poi>
        <javamelody>1.79.0</javamelody>
        <findbugs.helper>3.0.2</findbugs.helper>
        <jsoup>1.12.1</jsoup>
        <im4java>1.4.0</im4java>
        <auth0.jwt>3.8.3</auth0.jwt>
        <metadataextractor>2.12.0</metadataextractor>
        <stax2api>4.2</stax2api>
 
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 
        <report.plugin.maven.inforeports>3.0.0</report.plugin.maven.inforeports>
        <report.plugin.maven.jxr>3.0.0</report.plugin.maven.jxr>
        <report.plugin.maven.javadoc>3.1.1</report.plugin.maven.javadoc>
        <report.plugin.maven.checkstyle>3.1.0</report.plugin.maven.checkstyle>
        <report.plugin.maven.checkstyle.checkstyleversion>8.25</report.plugin.maven.checkstyle.checkstyleversion>
        <checkstyle.config.location>checkstyle.xml</checkstyle.config.location>
        <report.plugin.maven.pmd>3.12.0</report.plugin.maven.pmd>
        <report.plugin.maven.pmd.pmdversion>6.18.0</report.plugin.maven.pmd.pmdversion>
        <report.plugin.maven.surefire>3.0.0-M3</report.plugin.maven.surefire>
        <report.plugin.codehaus.findbugs>3.0.5</report.plugin.codehaus.findbugs>
        <report.plugin.codehaus.taglist>2.4</report.plugin.codehaus.taglist>
        <report.plugin.codehaus.versions>2.7</report.plugin.codehaus.versions>
 
        <build.plugin.maven.site>3.8.2</build.plugin.maven.site>
        <build.plugin.maven.compiler>3.8.1</build.plugin.maven.compiler>
        <build.plugin.maven.deploy>3.0.0-M1</build.plugin.maven.deploy>
        <build.plugin.maven.clean>3.1.0</build.plugin.maven.clean>
        <build.plugin.maven.install>3.0.0-M1</build.plugin.maven.install>
        <build.plugin.maven.dependency>3.1.1</build.plugin.maven.dependency>
        <build.plugin.maven.war>3.2.3</build.plugin.maven.war>
        <build.plugin.maven.resources>3.1.0</build.plugin.maven.resources>
        <build.plugin.maven.surefire>3.0.0-M3</build.plugin.maven.surefire>
        <build.plugin.maven.antrun>1.8</build.plugin.maven.antrun>
        <build.plugin.maven.assembly>3.1.1</build.plugin.maven.assembly>
        <build.plugin.maven.release>2.5.3</build.plugin.maven.release>
    </properties>
 
    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>${javax.servletapi}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>javax.servlet.jsp-api</artifactId>
            <version>${javax.servletjspapi}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.el</groupId>
            <artifactId>javax.el-api</artifactId>
            <version>${javax.elapi}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.taglibs</groupId>
            <artifactId>taglibs-standard-spec</artifactId>
            <version>${apache.taglibs}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.taglibs</groupId>
            <artifactId>taglibs-standard-impl</artifactId>
            <version>${apache.taglibs}</version>
        </dependency>
        <dependency>
            <groupId>org.im4java</groupId>
            <artifactId>im4java</artifactId>
            <version>${im4java}</version>
        </dependency>
        <!-- Spring dependencies -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring}</version>
        </dependency>
        <!-- Spring Security -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>${springsecurity}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>${springsecurity}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-taglibs</artifactId>
            <version>${springsecurity}</version>
        </dependency>
        <!-- DBCP (database) -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>${dbcp}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>${commons.lang}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-text</artifactId>
            <version>${commons.text}</version>
        </dependency>
        <!-- Mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql}</version>
        </dependency>
        <!-- Hibernate -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate}</version>
            <exclusions>
                <exclusion>
                    <groupId>com.fasterxml</groupId>
                    <artifactId>classmate</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>${hibernate.validator}</version>
        </dependency>
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>${jsoup}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>${jackson}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson}</version>
            <exclusions>
                <exclusion>
                    <groupId>com.fasterxml.jackson.core</groupId>
                    <artifactId>jackson-annotations</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>${jackson}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
            <version>${jackson}</version>
            <exclusions>
                <exclusion>
                    <groupId>com.fasterxml.jackson.core</groupId>
                    <artifactId>jackson-annotations</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-csv</artifactId>
            <version>${jackson}</version>
            <exclusions>
                <exclusion>
                    <groupId>com.fasterxml.jackson.core</groupId>
                    <artifactId>jackson-annotations</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>${jackson}</version>
            <exclusions>
                <exclusion>
                    <groupId>com.fasterxml.jackson.core</groupId>
                    <artifactId>jackson-annotations</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-hibernate5</artifactId>
            <version>${jackson}</version>
            <exclusions>
                <exclusion>
                    <groupId>com.fasterxml.jackson.core</groupId>
                    <artifactId>jackson-annotations</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>${log4j}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>${log4j}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-web</artifactId>
            <version>${log4j}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>${log4j}</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>${commons.io}</version>
        </dependency>
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>${commons.beanutils}</version>
        </dependency>
        <dependency>
            <groupId>com.sun.mail</groupId>
            <artifactId>javax.mail</artifactId>
            <version>${javax.mail}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>${apache.poi}</version>
            <exclusions>
                <exclusion>
                    <groupId>commons-codec</groupId>
                    <artifactId>commons-codec</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>${apache.poi}</version>
        </dependency>
        <dependency>
            <groupId>net.bull.javamelody</groupId>
            <artifactId>javamelody-core</artifactId>
            <version>${javamelody}</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.findbugs</groupId>
            <artifactId>jsr305</artifactId>
            <version>${findbugs.helper}</version>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>${auth0.jwt}</version>
        </dependency>
        <dependency>
            <groupId>com.drewnoakes</groupId>
            <artifactId>metadata-extractor</artifactId>
            <version>${metadataextractor}</version>
        </dependency>
    </dependencies>
 
    <reporting>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-project-info-reports-plugin</artifactId>
                <version>${report.plugin.maven.inforeports}</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jxr-plugin</artifactId>
                <version>${report.plugin.maven.jxr}</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-report-plugin</artifactId>
                <version>${report.plugin.maven.surefire}</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
                <version>${report.plugin.maven.javadoc}</version>
                <configuration>
                    <doclint>none</doclint>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-checkstyle-plugin</artifactId>
                <version>${report.plugin.maven.checkstyle}</version>
                <reportSets>
                    <reportSet>
                        <reports>
                            <report>checkstyle</report>
                        </reports>
                    </reportSet>
                </reportSets>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>taglist-maven-plugin</artifactId>
                <version>${report.plugin.codehaus.taglist}</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-pmd-plugin</artifactId>
                <version>${report.plugin.maven.pmd}</version>
                <configuration>
                    <skipEmptyReport>false</skipEmptyReport>
                    <analysisCache>true</analysisCache>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>findbugs-maven-plugin</artifactId>
                <version>${report.plugin.codehaus.findbugs}</version>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>versions-maven-plugin</artifactId>
                <version>${report.plugin.codehaus.versions}</version>
                <reportSets>
                    <reportSet>
                        <reports>
                            <report>dependency-updates-report</report>
                            <report>plugin-updates-report</report>
                            <report>property-updates-report</report>
                        </reports>
                    </reportSet>
                </reportSets>
            </plugin>
        </plugins>
    </reporting>
 
    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-pmd-plugin</artifactId>
                    <version>${report.plugin.maven.pmd}</version>
                    <dependencies>
                        <dependency>
                            <groupId>net.sourceforge.pmd</groupId>
                            <artifactId>pmd-core</artifactId>
                            <version>${report.plugin.maven.pmd.pmdversion}</version>
                        </dependency>
                        <dependency>
                            <groupId>net.sourceforge.pmd</groupId>
                            <artifactId>pmd-java</artifactId>
                            <version>${report.plugin.maven.pmd.pmdversion}</version>
                        </dependency>
                        <dependency>
                            <groupId>net.sourceforge.pmd</groupId>
                            <artifactId>pmd-javascript</artifactId>
                            <version>${report.plugin.maven.pmd.pmdversion}</version>
                        </dependency>
                        <dependency>
                            <groupId>net.sourceforge.pmd</groupId>
                            <artifactId>pmd-jsp</artifactId>
                            <version>${report.plugin.maven.pmd.pmdversion}</version>
                        </dependency>
                    </dependencies>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-checkstyle-plugin</artifactId>
                    <version>${report.plugin.maven.checkstyle}</version>
                    <dependencies>
                        <dependency>
                            <groupId>com.puppycrawl.tools</groupId>
                            <artifactId>checkstyle</artifactId>
                            <version>${report.plugin.maven.checkstyle.checkstyleversion}</version>
                        </dependency>
                    </dependencies>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-antrun-plugin</artifactId>
                    <version>${build.plugin.maven.antrun}</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-assembly-plugin</artifactId>
                    <version>${build.plugin.maven.assembly}</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-release-plugin</artifactId>
                    <version>${build.plugin.maven.release}</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-dependency-plugin</artifactId>
                    <version>${build.plugin.maven.dependency}</version>
                </plugin>
            </plugins>
        </pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-site-plugin</artifactId>
                <version>${build.plugin.maven.site}</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${build.plugin.maven.compiler}</version>
                <configuration>
                    <encoding>${project.build.sourceEncoding}</encoding>
                    <source>${maven.compiler.source}</source>
                    <target>${maven.compiler.target}</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-deploy-plugin</artifactId>
                <version>${build.plugin.maven.deploy}</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-clean-plugin</artifactId>
                <version>${build.plugin.maven.clean}</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-install-plugin</artifactId>
                <version>${build.plugin.maven.install}</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>${build.plugin.maven.war}</version>
                <configuration>
                    <useCache>false</useCache>
                    <packagingExcludes>
                        static/Merke/**
                    </packagingExcludes>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>${build.plugin.maven.resources}</version>
                <configuration>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${build.plugin.maven.surefire}</version>
                <configuration>
                    <skipTests>${skipTests}</skipTests>
                    <argLine>-XX:-UseSplitVerifier</argLine>
                </configuration>
            </plugin>
        </plugins>
    </build>
 
</project>

This will set most of your dependencies straight,
Be sure to check the graph to solve the dependency conflicts.
The parent pom ensures all projects use UTF-8 encoding for files & sets some maven plugins.

Checkstyle configuration

This xml file is based on sun_checks (removed some annoying ones). Place this in the ROOT folder of your webapp (next to pom.xml) as checkstyle.xml

checkstyle.xml
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
          "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
          "http://checkstyle.sourceforge.net/dtds/configuration_1_3.dtd">
 
<!--
 
  Checkstyle configuration that checks the sun coding conventions from:
 
    - the Java Language Specification at
      http://java.sun.com/docs/books/jls/second_edition/html/index.html
 
    - the Sun Code Conventions at http://java.sun.com/docs/codeconv/
 
    - the Javadoc guidelines at
      http://java.sun.com/j2se/javadoc/writingdoccomments/index.html
 
    - the JDK Api documentation http://java.sun.com/j2se/docs/api/index.html
 
    - some best practices
 
  Checkstyle is very configurable. Be sure to read the documentation at
  http://checkstyle.sf.net (or in your downloaded distribution).
 
  Most Checks are configurable, be sure to consult the documentation.
 
  To completely disable a check, just comment it out or delete it from the file.
 
  Finally, it is worth reading the documentation.
 
-->
 
<module name="Checker">
    <!--
        If you set the basedir property below, then all reported file
        names will be relative to the specified directory. See
        http://checkstyle.sourceforge.net/5.x/config.html#Checker
 
        <property name="basedir" value="${basedir}"/>
    -->
 
    <property name="fileExtensions" value="java, properties, xml"/>
 
    <!-- Checks that a package-info.java file exists for each package.     -->
    <!-- See http://checkstyle.sf.net/config_javadoc.html#JavadocPackage -->
    <!--module name="JavadocPackage"/-->
 
    <!-- Checks whether files end with a new line.                        -->
    <!-- See http://checkstyle.sf.net/config_misc.html#NewlineAtEndOfFile -->
    <module name="NewlineAtEndOfFile"/>
 
    <!-- Checks that property files contain the same keys.         -->
    <!-- See http://checkstyle.sf.net/config_misc.html#Translation -->
    <module name="Translation"/>
 
    <!-- Checks for Size Violations.                    -->
    <!-- See http://checkstyle.sf.net/config_sizes.html -->
    <module name="FileLength"/>
 
    <!-- Checks for whitespace                               -->
    <!-- See http://checkstyle.sf.net/config_whitespace.html -->
    <module name="FileTabCharacter"/>
 
    <!-- Miscellaneous other checks.                   -->
    <!-- See http://checkstyle.sf.net/config_misc.html -->
    <module name="RegexpSingleline">
        <property name="format" value="\s+$"/>
        <property name="minimum" value="0"/>
        <property name="maximum" value="0"/>
        <property name="message" value="Line has trailing spaces."/>
    </module>
 
    <!-- Checks for Size Violations.                    -->
    <!-- See http://checkstyle.sf.net/config_sizes.html -->
    <module name="LineLength">
        <property name="max" value="120"/>
    </module>
 
    <!-- Checks for Headers                                -->
    <!-- See http://checkstyle.sf.net/config_header.html   -->
    <!-- <module name="Header"> -->
    <!--   <property name="headerFile" value="${checkstyle.header.file}"/> -->
    <!--   <property name="fileExtensions" value="java"/> -->
    <!-- </module> -->
 
    <module name="TreeWalker">
 
        <!-- Checks for Javadoc comments.                     -->
        <!-- See http://checkstyle.sf.net/config_javadoc.html -->
        <!--module name="JavadocMethod"/-->
        <!--module name="JavadocType"/-->
        <!--module name="JavadocVariable"/-->
        <!--module name="JavadocStyle"/-->
 
        <!-- Checks for Naming Conventions.                  -->
        <!-- See http://checkstyle.sf.net/config_naming.html -->
        <module name="ConstantName"/>
        <module name="LocalFinalVariableName"/>
        <module name="LocalVariableName"/>
        <module name="MemberName"/>
        <module name="MethodName"/>
        <module name="PackageName"/>
        <module name="ParameterName"/>
        <module name="StaticVariableName"/>
        <module name="TypeName"/>
 
        <!-- Checks for imports                              -->
        <!-- See http://checkstyle.sf.net/config_import.html -->
        <module name="AvoidStarImport"/>
        <module name="IllegalImport"/> <!-- defaults to sun.* packages -->
        <module name="RedundantImport"/>
        <module name="UnusedImports">
            <property name="processJavadoc" value="false"/>
        </module>
        <module name="MethodLength"/>
        <module name="ParameterNumber"/>
 
        <!-- Checks for whitespace                               -->
        <!-- See http://checkstyle.sf.net/config_whitespace.html -->
        <module name="EmptyForIteratorPad"/>
        <module name="GenericWhitespace"/>
        <module name="MethodParamPad"/>
        <module name="NoWhitespaceAfter"/>
        <module name="NoWhitespaceBefore"/>
        <module name="OperatorWrap"/>
        <module name="ParenPad"/>
        <module name="TypecastParenPad"/>
        <module name="WhitespaceAfter"/>
        <module name="WhitespaceAround"/>
 
        <!-- Modifier Checks                                    -->
        <!-- See http://checkstyle.sf.net/config_modifiers.html -->
        <module name="ModifierOrder"/>
        <module name="RedundantModifier"/>
 
        <!-- Checks for blocks. You know, those {}'s         -->
        <!-- See http://checkstyle.sf.net/config_blocks.html -->
        <module name="AvoidNestedBlocks"/>
        <module name="EmptyBlock"/>
        <module name="LeftCurly"/>
        <module name="NeedBraces"/>
        <module name="RightCurly"/>
 
        <!-- Checks for common coding problems               -->
        <!-- See http://checkstyle.sf.net/config_coding.html -->
        <module name="AvoidInlineConditionals"/>
        <module name="EmptyStatement"/>
        <module name="EqualsHashCode"/>
        <!--module name="HiddenField"/-->
        <module name="IllegalInstantiation"/>
        <module name="InnerAssignment"/>
        <module name="MagicNumber"/>
        <module name="MissingSwitchDefault"/>
        <module name="SimplifyBooleanExpression"/>
        <module name="SimplifyBooleanReturn"/>
 
        <!-- Checks for class design                         -->
        <!-- See http://checkstyle.sf.net/config_design.html -->
        <!--module name="DesignForExtension"/-->
        <module name="FinalClass"/>
        <module name="HideUtilityClassConstructor"/>
        <module name="InterfaceIsType"/>
        <module name="VisibilityModifier"/>
 
        <!-- Miscellaneous other checks.                   -->
        <!-- See http://checkstyle.sf.net/config_misc.html -->
        <module name="ArrayTypeStyle"/>
        <module name="FinalParameters"/>
        <module name="TodoComment"/>
        <module name="UpperEll"/>
 
    </module>
 
</module>

Possible extras

JSTL (Java Standard Tag Libraries), using an implementation which doesn't potentially break your server.

        <dependency>
            <groupId>org.apache.taglibs</groupId>
            <artifactId>taglibs-standard-spec</artifactId>
            <version>1.2.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.taglibs</groupId>
            <artifactId>taglibs-standard-impl</artifactId>
            <version>1.2.5</version>
        </dependency>

Apache Tika (for textextraction purposes)

        <dependency>
            <groupId>org.apache.tika</groupId>
            <artifactId>tika-parsers</artifactId>
            <version>1.22</version>
        </dependency>

Hibernate search (for textsearching purposes / index)

<dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-search-orm</artifactId>
   <version>5.11.3.Final</version>
</dependency>

Hibernate validator for java < 8 (for validation purposes, also combine with jsoup to scan for scripts in html)

<dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-validator</artifactId>
   <version>5.4.3.Final</version>
</dependency>

Hibernate validator for java 8+ (for validation purposes, also combine with jsoup to scan for scripts in html)

<dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-validator</artifactId>
   <version>6.0.17.Final</version>
</dependency>

JSoup for scanning html

<dependency>
   <groupId>org.jsoup</groupId>
   <artifactId>jsoup</artifactId>
   <version>1.12.1</version>
</dependency>

context.xml :

We're going to set some properties in META-INF/context.xml
if path = ”/” it will deploy locally to your server root. To get the same result on a live server, you need to rename the war to ROOT.war.
if path = ”/applicationname” it will deploy locally to /applicationname. To get the same result on a live server, you need to rename the war to applicationname.war.

webAppRootKey: an identifier for the applciation, give it the value of your applicationname
database_user: make a database user like applicationname_user, you can make the same one on the live server (with same password).
database_catalog: applicationname
database_jdbc: notice extra options useSSL (false) and sendFractionalSeconds (false) for compatibility with mysql 5.7
searchindexPath: remove entry if not using hibernate search, change applicationname otherwise.
uploadPath: change applicationname, don't forget to create the path on server + locally. also make sure to chmod -R 777

context.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context antiJARLocking="true" path="/">
    <Parameter name="webAppRootKey" value="applicationname" />
    <Parameter name="log4jConfiguration" value="/WEB-INF/log4j2.json" />
    <Parameter name="database_user" value="applicationname_user"/>
    <Parameter name="database_password" value="somethingcomplicated"/>
    <Parameter name="database_jdbc" value="jdbc:mysql://localhost:3306/?useUnicode=true&amp;characterEncoding=UTF-8&amp;useSSL=false&amp;sendFractionalSeconds=false&amp;serverTimezone=Europe/Berlin"/>
    <Parameter name="database_catalog" value="applicationname"/>
    <Parameter name="searchindexPath" value="/var/mentoringsystems/applicationname/index"/>
    <Parameter name="uploadPath" value="/var/mentoringsystems/applicationname"/>
    <Parameter name="email_host" value="mailserver"/>
    <Parameter name="email_port" value="587"/>
    <Parameter name="email_user" value="registration@merke.be"/>
    <Parameter name="email_pass" value="somethingcomplicated"/>
    <Parameter name="email_auth" value="true"/>
    <Parameter name="email_starttls" value="true"/>
    <Parameter name="email_from" value="registration@merke.be"/>
    <Parameter name="feedback_from" value="feedback@merke.be"/>
    <Parameter name="feedback_to" value="support@mentoringsystems.be"/>
    <Parameter name="app_url" value="https://merke.be/"/>
    <Parameter name="development" value="true"/>
</Context>

web.xml :

Now we need the following in our WEB-INF/web.xml
Don't forget to change applicationname.

web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    <display-name>applicationname</display-name>
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
</web-app>

Java Configuration classes

Why?

We want to create a smaller application with less dependencies & less configuration (configuration by convention).
Properties which are app dependent can still be placed in META-INF/context.xml

AppConfiguration

AppConfiguration.java
package be.mentoringsystems.merke.config;
 
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
 
/**
 *
 * @author anthonyarents
 */
@Configuration
@ComponentScan({ "be.mentoringsystems.merke.*" })
@EnableScheduling
@Import({MvcConfiguration.class, DatabaseConfiguration.class, SecurityConfiguration.class, MessageConfiguration.class, MailConfiguration.class, AsyncConfiguration.class})
public class AppConfiguration {
 
}

MvcConfiguration

MvcConfiguration.java
package be.mentoringsystems.merke.config;
 
import be.mentoringsystems.merke.presentation.locale.CustomLocaleResolver;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import java.nio.charset.Charset;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Locale;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.MediaType;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
 
/**
 *
 * @author anthonyarents
 */
@Configuration
@EnableWebMvc
public class MvcConfiguration implements WebMvcConfigurer {
 
    private static final int COOKIE_MAX_AGE = 4800;
    private static final int CACHEPERIOD_IMAGES = 2678400;
    private static final int CACHEPERIOD_JS = 604800;
    @Value("#{contextParameters.development}")
    private Boolean development;
 
    @Override
    public void configureDefaultServletHandling(final DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
 
    @Override
    public void configureContentNegotiation(final ContentNegotiationConfigurer configurer) {
        configurer.favorPathExtension(false).favorParameter(true);
        configurer.defaultContentType(MediaType.TEXT_PLAIN);
        configurer.useRegisteredExtensionsOnly(true);
        configurer.mediaType("txt", MediaType.TEXT_PLAIN);
        configurer.mediaType("json", MediaType.APPLICATION_JSON);
        configurer.mediaType("xml", MediaType.APPLICATION_XML);
        configurer.mediaType("html", MediaType.TEXT_HTML);
    }
 
    @Override
    public void configureMessageConverters(final List<HttpMessageConverter<?>> converters) {
        final Hibernate5Module hibernate5Module = new Hibernate5Module();
        hibernate5Module.configure(Hibernate5Module.Feature.FORCE_LAZY_LOADING, false);
        hibernate5Module.configure(Hibernate5Module.Feature.USE_TRANSIENT_ANNOTATION, false);
 
        final JavaTimeModule javaTimeModule = new JavaTimeModule();
 
        final DateTimeFormatter localDateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(localDateTimeFormatter));
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(localDateTimeFormatter));
 
        final Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
        builder.indentOutput(true);
        builder.defaultViewInclusion(true);
        builder.modules(hibernate5Module, javaTimeModule);
        builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
 
        converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
 
        final StringHttpMessageConverter stringMessageConverter = new StringHttpMessageConverter(Charset.forName("UTF-8"));
        stringMessageConverter.setWriteAcceptCharset(false);
        converters.add(stringMessageConverter);
 
        converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
        converters.add(new ByteArrayHttpMessageConverter());
 
    }
 
    @Override
    public void addResourceHandlers(final ResourceHandlerRegistry registry) {
        // images
        registry.addResourceHandler("/static/favicon/**").addResourceLocations("/static/favicon/").setCachePeriod(CACHEPERIOD_IMAGES);
        registry.addResourceHandler("/static/**").addResourceLocations("/static/").setCachePeriod(604800);
 
        if (Boolean.TRUE.equals(development)) {
            // development
            registry.addResourceHandler("/static/Merke/**").addResourceLocations("/static/Merke/").setCachePeriod(CACHEPERIOD_JS);
            registry.addResourceHandler("/staticresources/**").addResourceLocations("/staticresources/").setCachePeriod(604800);
            registry.addResourceHandler("/build/classic/resources/**").addResourceLocations("/build/classic/resources/").setCachePeriod(604800);
            registry.addResourceHandler("/build/modern/resources/**").addResourceLocations("/build/modern/resources/").setCachePeriod(604800);
            registry.addResourceHandler("/build/resources/**").addResourceLocations("/build/resources/").setCachePeriod(604800);
            registry.addResourceHandler("/**").addResourceLocations("/static/Merke/").setCachePeriod(604800);
        } else {
            // production
            registry.addResourceHandler("/static/Merke/**").addResourceLocations("/static/Merke/").setCachePeriod(CACHEPERIOD_JS);
            registry.addResourceHandler("/staticresources/**").addResourceLocations("/staticresources/").setCachePeriod(604800);
            registry.addResourceHandler("/**").addResourceLocations("/build/").setCachePeriod(604800);
        }
    }
 
    @Override
    public void configureViewResolvers(final ViewResolverRegistry registry) {
        registry.jsp("/WEB-INF/jsp/", ".jsp");
    }
 
    @Override
    public void addViewControllers(final ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("forward:/index.html");
        registry.addViewController("/login").setViewName("redirect:/");
        registry.addViewController("/favicon.ico").setViewName("forward:/static/favicon/favicon.ico");
        registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
    }
 
    @Bean
    public LocaleResolver localeResolver() {
        CustomLocaleResolver resolver = new CustomLocaleResolver();
        resolver.setDefaultLocale(new Locale("nl"));
        resolver.setCookieName("myLocaleCookie");
        resolver.setCookieMaxAge(COOKIE_MAX_AGE);
        return resolver;
    }
 
    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
        interceptor.setParamName("mylocale");
        return interceptor;
    }
 
    @Override
    public void addInterceptors(final InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
    }
 
    @Bean(name = "multipartResolver")
    public StandardServletMultipartResolver multipartResolver() {
        return new StandardServletMultipartResolver();
    }
 
    @Override
    public void addCorsMappings(final CorsRegistry registry) {
        registry.addMapping("/**");
    }
 
}

DatabaseConfiguration

DatabaseConfiguration.java
package be.mentoringsystems.merke.config;
 
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
 
/**
 *
 * @author anthonyarents
 */
@Configuration
@EnableTransactionManagement
public class DatabaseConfiguration {
 
    @Value("#{contextParameters.database_catalog}")
    private String dbName;
    @Value("#{contextParameters.database_user}")
    private String dbUser;
    @Value("#{contextParameters.database_password}")
    private String dbPass;
    @Value("#{contextParameters.database_jdbc}")
    private String dbJdbc;
    @Value("#{contextParameters.searchindexPath}")
    private String searchindexPath;
 
    private static final int DSPOOL_MAX_IDLE = 17;
    private static final int DSPOOL_MAX_TOTAL = 20;
    private static final int DSPOOL_MAX_WAIT_MILLIS = 10000;
 
    @Bean
    public DataSource dataSource() {
        final BasicDataSource bds = new BasicDataSource();
        bds.setUsername(dbUser);
        bds.setPassword(dbPass);
        bds.setDefaultCatalog(dbName);
        bds.setUrl(dbJdbc);
        bds.setDriverClassName("com.mysql.cj.jdbc.Driver");
        bds.setMaxIdle(DSPOOL_MAX_IDLE);
        bds.setMaxTotal(DSPOOL_MAX_TOTAL);
        bds.setMaxWaitMillis(DSPOOL_MAX_WAIT_MILLIS);
        bds.setValidationQuery("SELECT 1;");
        bds.setTestOnBorrow(true);
        bds.setRemoveAbandonedOnBorrow(true);
        return bds;
    }
 
    private Properties getHibernateProperties() {
        Properties properties = new Properties();
        properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
        properties.put("hibernate.show_sql", false);
        properties.put("hibernate.format_sql", false);
        properties.put("hibernate.connection.useUnicode", true);
        properties.put("hibernate.connection.characterEncoding", "utf8");
        properties.put("hibernate.connection.charSet", "utf8");
        properties.put("hibernate.search.default.directory_provider", "filesystem");
        properties.put("hibernate.search.default.indexBase", searchindexPath);
        return properties;
    }
 
    @Bean
    public LocalSessionFactoryBean sessionFactory() {
        LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
        sessionFactory.setDataSource(dataSource());
        sessionFactory.setAnnotatedPackages(new String[]{"be.mentoringsystems.merke.model.db"});
        sessionFactory.setPackagesToScan(new String[]{"be.mentoringsystems.merke.model.db"});
        sessionFactory.setHibernateProperties(getHibernateProperties());
        return sessionFactory;
    }
 
    @Bean
    @Autowired
    public HibernateTransactionManager transactionManager(final SessionFactory s) {
        HibernateTransactionManager txManager = new HibernateTransactionManager();
        txManager.setSessionFactory(s);
        return txManager;
    }
}

SecurityConfiguration

SecurityConfiguration.java
package be.mentoringsystems.merke.config;
 
import be.mentoringsystems.merke.security.JWTAuthenticationFilter;
import be.mentoringsystems.merke.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.switchuser.SwitchUserFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
 
/**
 *
 * @author anthonyarents
 */
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
 
    @Autowired
    private LoginService loginService;
    @Autowired
    private JWTAuthenticationFilter jwtAuthenticationFilter;
 
    @Override
    public void configure(final WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/static/**", "/classic/**", "/app/**", "/production/**", "/Merke/**");
    }
 
    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        // temporary
        http.csrf().disable();
        http.headers().frameOptions().sameOrigin();
        http.userDetailsService(loginService);
        http
                .addFilter(switchUserFilter())
                .addFilterAfter(jwtAuthenticationFilter, BasicAuthenticationFilter.class)
                //.formLogin()
                //.loginPage("/login").defaultSuccessUrl("/", false).failureUrl("/login?error").permitAll() //login page, redirect tologinrequired page if there was one, permitall is necessary
                //.and()
                .logout().logoutUrl("/logout").logoutSuccessUrl("/").permitAll() // logout page, permitall is necessary
                .and()
                .authorizeRequests() // request matching with ant
                .antMatchers("/**").permitAll()
                .antMatchers("/login/**").permitAll()
                .antMatchers("/logins/authenticate").permitAll()
                .antMatchers("/register").permitAll()
                .antMatchers("/categories").permitAll()
                .antMatchers("/venues").permitAll()
                .antMatchers("/privateVenues").permitAll()
                .antMatchers("/recoverPassword").anonymous()
                .antMatchers("/passwordrecovery").anonymous()
                .antMatchers("/switchuser").hasRole("MSADMIN")
                .anyRequest().authenticated(); // all remaining requests need login
    }
 
    @Bean(name = "switchUserFilter")
    public SwitchUserFilter switchUserFilter() {
        final SwitchUserFilter filter = new SwitchUserFilter();
        filter.setUserDetailsService(loginService);
        filter.setExitUserUrl("/exituser");
        filter.setSwitchUserUrl("/switchuser");
        filter.setTargetUrl("/");
        filter.setUsernameParameter("username");
        return filter;
    }
 
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
 
    @Override
    protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(loginService).passwordEncoder(passwordEncoder());
    }
 
}

Spring Webapplication Initializer

removes web.xml config: the servlet code

SpringConfigurationInitializer.java
package be.trustandchain.mobi.config.initializer;
 
import be.mentoringsystems.merke.config.AppConfiguration;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletRegistration;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
 
/**
 *
 * @author anthonyarents
 */
public class SpringConfigurationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
 
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{AppConfiguration.class};
    }
 
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return null;
    }
 
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
 
    @Override
    protected void customizeRegistration(final ServletRegistration.Dynamic registration) {
        MultipartConfigElement multipartConfigElement = new MultipartConfigElement("/tmp", mb(50), mb(70), 0);
        registration.setMultipartConfig(multipartConfigElement);
    }
 
    private long mb(final long nr) {
        // 1024 * 1024
        return nr * 1048576;
    }
 
}

Spring Security Initializer

removes web.xml config: filterchain

SpringSecurityInitializer.java
package be.mentoringsystems.merke.config.initializer;
 
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
 
/**
 *
 * @author anthonyarents
 */
public class SpringSecurityInitializer extends AbstractSecurityWebApplicationInitializer {
 
}

Optional Java Configuration classes

These are examples of extra configuration. The class needs to be added to AppConfiguration's @Import !

Mail configuration

used for configuring a javamailer

MailConfiguration.java
package be.mentoringsystems.merke.config;
 
import java.util.Properties;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
 
/**
 *
 * @author anthonyarents
 */
@Configuration
public class MailConfiguration {
 
    @Value("#{contextParameters.email_host}")
    private String host;
    @Value("#{contextParameters.email_user}")
    private String user;
    @Value("#{contextParameters.email_pass}")
    private String pass;
    @Value("#{contextParameters.email_port}")
    private Integer port;
    @Value("#{contextParameters.email_auth}")
    private Boolean auth;
    @Value("#{contextParameters.email_starttls}")
    private Boolean starttls;
 
    @Bean(name = "javaMailService")
    public JavaMailSender javaMailService() {
        JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
        javaMailSender.setDefaultEncoding("UTF-8");
        javaMailSender.setHost(host);
        javaMailSender.setUsername(user);
        javaMailSender.setPassword(pass);
        javaMailSender.setPort(port);
        final Properties properties = new Properties();
        properties.setProperty("mail.smtp.auth", Boolean.toString(auth));
        properties.setProperty("mail.smtp.starttls.enable", Boolean.toString(starttls));
        javaMailSender.setJavaMailProperties(properties);
        return javaMailSender;
    }
}

Message configuration

used for translation messages

MessageConfiguration.java
package be.mentoringsystems.merke.config;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
 
/**
 *
 * @author anthonyarents
 */
@Configuration
public class MessageConfiguration {
 
    @Bean
    public ResourceBundleMessageSource messageSource() {
        ResourceBundleMessageSource source = new ResourceBundleMessageSource();
        source.setBasename("i18n/messages");
        source.setUseCodeAsDefaultMessage(true);
        return source;
    }
}

Async configuration

used for async method configuration, with this it's possible to use @Async for methods you want to run async
(like the method in charge of sending a mail).

AsyncConfiguration.java
package be.mentoringsystems.merke.config;
 
import java.util.concurrent.Executor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 
/**
 *
 * @author anthonyarents
 */
@Configuration
@EnableAsync
public class AsyncConfiguration extends AsyncConfigurerSupport {
 
    @Bean
    public TaskScheduler taskScheduler() {
        return new ConcurrentTaskScheduler();
    }
 
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(2);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("Async-");
        executor.setAwaitTerminationSeconds(10);
        executor.initialize();
        return executor;
    }
}

Conditional configuration

Sometimes, we want certain beans to be used in production but not in test, activate blockchain or disable it, …
We can do this by implementing Condition, in this condition we check the contextParameters, the same code can be used to check
environment variables etc.

BlockchainUsageCondition.java
package be.trustandchain.mobi.condition;
 
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
 
/**
 *
 * @author anthonyarents
 */
public class BlockchainUsageCondition implements Condition {
 
    @Override
    public boolean matches(final ConditionContext cc, final AnnotatedTypeMetadata atm) {
        String value = cc.getEnvironment().getProperty("use_blockchain");
        return "true".equals(value);
    }
 
}


Classes annotated with @Conditional will only be in effect IF the condition returns true :

import org.springframework.context.annotation.Conditional;
 
@Repository
@Conditional(BlockchainUsageCondition.class)
public class CommentDAOImpl implements CommentDAO { ... }

Database creation

Create a database WITH utf8-bin (case sensitive)

Login.sql
CREATE TABLE `Login` (
  `id` BINARY(36) NOT NULL,
  `firstname` VARCHAR(200) COLLATE utf8_bin NOT NULL,
  `lastname` VARCHAR(200) COLLATE utf8_bin NOT NULL,
  `email` VARCHAR(400) COLLATE utf8_bin NOT NULL,
  `password` BINARY(60) NOT NULL,
  `group` INT(1) NOT NULL,
  `username` VARCHAR(200) COLLATE utf8_bin NOT NULL,
  `deleted` CHAR(1) COLLATE utf8_bin NOT NULL DEFAULT 'N',
  `active` CHAR(1) COLLATE utf8_bin NOT NULL DEFAULT 'Y',
  `createdOn` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
 
ALTER TABLE `Login`
  ADD PRIMARY KEY (`id`),
  ADD KEY `username` (`username`);

Model - Service - DAO - Presentation

QueryParams

QueryParams.java
package be.mentoringsystems.merke.model;
 
import be.mentoringsystems.merke.helper.ConversionHelper;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
 
/**
 *
 * @author anthonyarents
 */
public class QueryParams {
 
    private int start = 0;
    private int limit = 0;
    private FilterMap filter = new FilterMap();
    private SortingMap sort = new SortingMap();
    private List<String> initializeExtra = new ArrayList<String>();
 
    // return message can be used to return statusses like "data generated" to other methods.
    private String returnmessage;
 
    public String getReturnmessage() {
        return returnmessage;
    }
 
    public void setReturnmessage(final String returnmessage) {
        this.returnmessage = returnmessage;
    }
 
    public String getLoadConfig() {
        return ConversionHelper.toString(this.getFilterValue("loadconfig"));
    }
 
    public void setLoadConfig(final String val) {
        this.addFilter("loadconfig", val);
    }
 
    public List<String> getInitializeExtra() {
        return initializeExtra;
    }
 
    public void setInitializeExtra(final List<String> initializeExtra) {
        this.initializeExtra = initializeExtra;
    }
 
    public void addInitializeExtra(final String property) {
        this.initializeExtra.add(property);
    }
 
    public FilterMap getFilter() {
        return filter;
    }
 
    public void setFilter(final FilterMap filter) {
        this.filter = filter;
    }
 
    public void setSearch(final String search) {
        this.addFilter("search", search);
    }
 
    @JsonAnySetter
    public void addFilter(final String property, final Object value) {
        if (!StringUtils.isEmpty(property) && !StringUtils.startsWith(property, "_")) {
            Filter filter = getFilter(property);
            if (filter == null) {
                filter = new Filter();
                filter.setProperty(property);
                filter.setValue(value);
                this.filter.addFilter(filter);
            } else {
                filter.setValue(value);
            }
        }
    }
 
    public void addSorting(final String property, final String direction) {
        if (!StringUtils.isEmpty(property)) {
            Sorting sorting = new Sorting();
            sorting.setProperty(property);
            sorting.setDirection(direction);
            getSort().getSortings().add(sorting);
        }
    }
 
    public Filter getFilter(final String property) {
        if (this.getFilter() == null) {
            return null;
        } else {
            return this.filter.getFilter(property);
        }
    }
 
    public Object getFilterValue(final String property) {
        if (this.getFilter() == null) {
            return null;
        } else {
            return this.getFilter().getValue(property);
        }
    }
 
    public int getLimit() {
        return limit;
    }
 
    public void setLimit(final int limit) {
        this.limit = limit;
    }
 
    public SortingMap getSort() {
        return sort;
    }
 
    public void setSort(final SortingMap sort) {
        this.sort = sort;
    }
 
    public int getStart() {
        return start;
    }
 
    public void setStart(final int start) {
        this.start = start;
    }
}

FilterMap

FilterMap.java
package be.mentoringsystems.merke.model;
 
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
/**
 *
 * @author anthonyarents
 */
public class FilterMap {
 
    private List<Filter> filters = new ArrayList<Filter>();
    private Map<String, Filter> map = new HashMap<String, Filter>();
 
    public List<Filter> getFilters() {
        return filters;
    }
 
    public void setFilters(final List<Filter> filters) {
        this.filters = filters;
    }
 
    public void addFilter(final Filter filter) {
        this.filters.add(filter);
        this.map.put(filter.getProperty(), filter);
    }
 
    public Map<String, Filter> getMap() {
        return map;
    }
 
    public void setMap(final Map<String, Filter> map) {
        this.map = map;
    }
 
    public Filter getFilter(final String property) {
        return map.get(property);
    }
 
    public Object getValue(final String property) {
        final Filter result = getFilter(property);
        if (result == null) {
            return null;
        } else {
            return result.getValue();
        }
    }
}

Filter

Filter.java
package be.mentoringsystems.merke.model;
 
/**
 *
 * @author anthonyarents
 */
public class Filter {
 
    private String property;
    private Object value;
 
    public String getProperty() {
        return property;
    }
 
    public void setProperty(final String property) {
        this.property = property;
    }
 
    public Object getValue() {
        return value;
    }
 
    public void setValue(final Object value) {
        this.value = value;
    }
}

SortingMap

SortingMap.java
package be.mentoringsystems.merke.model;
 
import java.util.ArrayList;
import java.util.List;
 
/**
 *
 * @author anthonyarents
 */
public class SortingMap {
 
    private List<Sorting> sortings = new ArrayList<>();
 
    public List<Sorting> getSortings() {
        return sortings;
    }
 
    public void setSortings(final List<Sorting> sortings) {
        this.sortings = sortings;
    }
}

Sorting

Sorting.java
package be.mentoringsystems.merke.model;
 
/**
 *
 * @author anthonyarents
 */
public class Sorting {
 
    private String property;
    private String direction;
 
    public String getDirection() {
        return direction;
    }
 
    public void setDirection(final String direction) {
        this.direction = direction;
    }
 
    public String getProperty() {
        return property;
    }
 
    public void setProperty(final String property) {
        this.property = property;
    }
}

Helpers

ConversionHelper.java
package be.mentoringsystems.merke.helper;
 
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
import org.apache.commons.lang3.math.NumberUtils;
 
/**
 *
 * @author anthonyarents
 */
public final class ConversionHelper {
 
    private static final DateTimeFormatter LOCALDATEFORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final DateTimeFormatter LOCALDATETIMEFORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
 
    private ConversionHelper() {
    }
 
    @SuppressWarnings("unchecked")
    public static List<Object> toObjectList(final Object obj) {
        List<Object> result = new ArrayList<Object>();
        if (obj == null) {
            return result;
        }
        if (obj instanceof List) {
            return (List<Object>) obj;
        } // assume it's always a list of string, this is done by spring automatically ...
        result.add(obj);
        return result;
    }
 
    @SuppressWarnings("unchecked")
    public static List<String> toStringList(final Object obj) {
        List<String> result = new ArrayList<String>();
        if (obj == null) {
            return result;
        }
        if (obj instanceof List) {
            return (List<String>) obj;
        } // assume it's always a list of string, this is done by spring automatically ...
        if (obj instanceof String) {
            result.add((String) obj);
        }
        return result;
    }
 
    @SuppressWarnings("unchecked")
    public static List<Integer> toIntegerList(final Object obj) {
        List<Integer> result = new ArrayList<Integer>();
        if (obj == null) {
            return result;
        }
        if (obj instanceof List) {
            return (List<Integer>) obj;
        } // assume it's always a list of integer, this is done by spring automatically ...
        if (obj instanceof Integer) {
            result.add((Integer) obj);
        }
        return result;
    }
 
    public static List<UUID> toUUIDList(final Object obj) {
        List<UUID> result = new ArrayList<UUID>();
        if (obj == null) {
            return result;
        }
        List<Object> start = toObjectList(obj);
        for (Object o : start) {
            UUID u = toUUID(o);
            if (u != null) {
                result.add(u);
            }
        }
        return result;
    }
 
    public static UUID toUUID(final Object obj) {
        if (obj == null) {
            return null;
        }
        if (obj instanceof UUID) {
            return (UUID) obj;
        }
        return UUID.fromString(toString(obj));
    }
 
    public static String toString(final Object obj) {
        if (obj == null) {
            return null;
        }
        if (obj instanceof String) {
            return (String) obj;
        }
        return obj.toString();
    }
 
    public static LocalDate toLocalDate(final Object obj) {
        if (obj == null) {
            return null;
        }
        if (obj instanceof LocalDate) {
            return (LocalDate) obj;
        }
        String dateString = toString(obj);
        if (dateString == null) {
            return null;
        } else {
            return (LocalDate) LOCALDATEFORMATTER.parse(dateString);
        }
    }
 
    public static LocalDateTime toLocalDateTime(final Object obj) {
        if (obj == null) {
            return null;
        }
        if (obj instanceof LocalDateTime) {
            return (LocalDateTime) obj;
        }
        String dateString = toString(obj);
        if (dateString == null) {
            return null;
        } else {
            return (LocalDateTime) LOCALDATETIMEFORMATTER.parse(dateString);
        }
    }
 
    public static Integer toInteger(final Object obj) {
        if (obj == null) {
            return null;
        }
        if (obj instanceof Integer) {
            return (Integer) obj;
        }
        return NumberUtils.toInt(toString(obj));
    }
 
    @Nullable
    public static Boolean toBoolean(final Object obj) {
        if (obj == null) {
            return null;
        }
        if (obj instanceof Boolean) {
            return (Boolean) obj;
        }
        String objString = toString(obj);
        if ("1".equals(objString) || "true".equals(objString)) {
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }
}
DTOHelper.java
package be.mentoringsystems.merke.helper;
 
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
 
/**
 *
 * @author anthonyarents
 */
public final class DTOHelper {
 
    private DTOHelper() {
    }
 
    public static void copyNonNull(final Object source, final Object target) {
        try {
            Map<String, Object> sourceValues = PropertyUtils.describe(source);
            Collection<Object> values = sourceValues.values();
            values.removeAll(Collections.singleton(null));
            populate(sourceValues, target);
        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
            Logger.getLogger(DTOHelper.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
 
    public static void copy(final Object source, final Object target) {
        try {
            Map<String, Object> sourceValues = PropertyUtils.describe(source);
            populate(sourceValues, target);
        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
            Logger.getLogger(DTOHelper.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
 
    public static void populate(final Map<String, Object> source, final Object target) throws IllegalAccessException, InvocationTargetException {
        BeanUtils.populate(target, source);
    }
}

Model

Of note :

  • implements UserDetails (Spring Security)
  • Uses JsonView, IF we want to have a specific view, we want to mark all “public” fields with the “View.Public”
    class and the “private” fields with a class which extends “View.Public” class, preferably “View.<Model>”

UUID Id

Login.java
package be.mentoringsystems.merke.model.db;
 
import be.mentoringsystems.merke.model.dto.View;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonView;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Transient;
import org.hibernate.annotations.Type;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
 
/**
 *
 * @author anthonyarents
 */
@Entity
public class Login implements UserDetails, Serializable {
 
    private static final long serialVersionUID = -5435867605493271271L;
    private UUID id;
    private String firstname;
    private String lastname;
    private String email;
    private String password;
    private Integer group = 2;
    private String username;
    private boolean deleted = false;
    private boolean active = false;
    private LocalDateTime createdOn = LocalDateTime.now();
    private List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
 
    @JsonIgnore
    @Column
    @Type(type = "yes_no")
    public boolean isDeleted() {
        return deleted;
    }
 
    public void setDeleted(boolean deleted) {
        this.deleted = deleted;
    }
 
    @JsonView(View.Public.class)
    @Id
    @Column
    @GeneratedValue( generator = "uuid" )
    @Type(type = "uuid-char")
    public UUID getId() {
        return id;
    }
 
    public void setId(final UUID id) {
        this.id = id;
    }
 
    @JsonView(View.Login.class)
    @Column
    public LocalDateTime getCreatedOn() {
        return createdOn;
    }
 
    public void setCreatedOn(final LocalDateTime createdOn) {
        this.createdOn = createdOn;
    }
 
    @JsonView(View.Login.class)
    @Column
    public String getEmail() {
        return email;
    }
 
    public void setEmail(final String email) {
        this.email = email;
    }
 
    @JsonView(View.Public.class)
    @Column
    public String getFirstname() {
        return firstname;
    }
 
    public void setFirstname(final String firstname) {
        this.firstname = firstname;
    }
 
    @JsonView(View.Login.class)
    @Column(name="`group`", nullable = false)
    public Integer getGroup() {
        return group;
    }
 
    public void setGroup(Integer group) {
        this.group = group;
    }
 
    @JsonView(View.Public.class)
    @Column
    public String getLastname() {
        return lastname;
    }
 
    public void setLastname(final String lastname) {
        this.lastname = lastname;
    }
 
    @JsonView(View.Login.class)
    @Column
    @Override
    public String getUsername() {
        return username;
    }
 
    public void setUsername(String username) {
        this.username = username;
    }
 
    @JsonIgnore
    @Column(nullable = false)
    @Override
    public String getPassword() {
        return password;
    }
 
    public void setPassword(final String password) {
        this.password = password;
    }
 
    @JsonView(View.Login.class)
    @Column
    @Type(type = "yes_no")
    public boolean isActive() {
        return active;
    }
 
    public void setActive(boolean active) {
        this.active = active;
    }
 
    @JsonIgnore
    @Override
    @Transient
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }
 
    public void setAuthorities(List<GrantedAuthority> authorities) {
        this.authorities = authorities;
    }
 
    @JsonIgnore
    @Override
    @Transient
    public boolean isAccountNonExpired() {
        return true;
    }
 
    @JsonIgnore
    @Override
    @Transient
    public boolean isAccountNonLocked() {
        return true;
    }
 
    @JsonIgnore
    @Override
    @Transient
    public boolean isCredentialsNonExpired() {
        return true;
    }
 
    @JsonIgnore
    @Override
    @Transient
    public boolean isEnabled() {
        return this.isActive();
    }
}

Important : “package-info.java” with uuid generator, to be added to model.db

package-info.java
@GenericGenerator(
        name = "uuid",
        strategy = "org.hibernate.id.UUIDGenerator",
        parameters = {
            @Parameter(
                    name = "uuid_gen_strategy_class",
                    value = "org.hibernate.id.uuid.CustomVersionOneStrategy"
            )
        }
)
@FilterDef(name = "deletedObjectsFilter", defaultCondition = "deleted = 'N'")
package be.mentoringsystems.merke.model.db;
 
import org.hibernate.annotations.FilterDef;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;

Integer ID

Context.java
package de.jcpis.analysestats.model.db;
 
import java.io.Serializable;
import java.util.UUID;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import org.hibernate.annotations.Type;
 
/**
 *
 * @author anthonyarents
 */
@Entity
public class Context implements Serializable {
 
    private static final long serialVersionUID = -7951893343401921083L;
    private Integer id;
    private Integer serverId;
    private Server server;
    private UUID contextId;
    private UUID parentContextId;
    private String name;
    private Integer beraterCount;
    private Integer mandantCount;
    private Integer assetCount;
    private Integer syncCount;
    private boolean hidden = false;
 
    @Id
    @Column
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Integer getId() {
        return id;
    }
 
    public void setId(Integer id) {
        this.id = id;
    }
 
    @Column(insertable = false, updatable = false)
    public Integer getServerId() {
        return serverId;
    }
 
    public void setServerId(Integer serverId) {
        this.serverId = serverId;
    }
 
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "serverId")
    public Server getServer() {
        return server;
    }
 
    public void setServer(Server server) {
        this.server = server;
    }
 
    @Column
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    @Column
    public Integer getBeraterCount() {
        return beraterCount;
    }
 
    public void setBeraterCount(Integer beraterCount) {
        this.beraterCount = beraterCount;
    }
 
    @Column
    public Integer getMandantCount() {
        return mandantCount;
    }
 
    public void setMandantCount(Integer mandantCount) {
        this.mandantCount = mandantCount;
    }
 
    @Column
    public Integer getAssetCount() {
        return assetCount;
    }
 
    public void setAssetCount(Integer assetCount) {
        this.assetCount = assetCount;
    }
 
    @Column
    public Integer getSyncCount() {
        return syncCount;
    }
 
    public void setSyncCount(Integer syncCount) {
        this.syncCount = syncCount;
    }
 
    @Column
    @Type(type = "uuid-char")
    public UUID getContextId() {
        return contextId;
    }
 
    public void setContextId(UUID contextId) {
        this.contextId = contextId;
    }
 
    @Column
    @Type(type = "uuid-char")
    public UUID getParentContextId() {
        return parentContextId;
    }
 
    public void setParentContextId(UUID parentContextId) {
        this.parentContextId = parentContextId;
    }
 
    @Column
    @Type(type = "yes_no")
    public boolean isHidden() {
        return hidden;
    }
 
    public void setHidden(boolean hidden) {
        this.hidden = hidden;
    }
 
}

Dynamic insert and update

Dynamic insert and update means hibernate will check and make a query with the changes only. This is handy for queries that grow too big (like multiple text columns).

@DynamicInsert
@DynamicUpdate

DAO Interface

Now we could use a DAO interface

LoginDAO.java
package be.mentoringsystems.merke.persistence;
 
import be.mentoringsystems.merke.model.QueryParams;
import be.mentoringsystems.merke.model.db.Login;
import java.util.List;
import java.util.UUID;
 
/**
 *
 * @author anthonyarents
 */
public interface LoginDAO {
 
    void save(Login obj);
 
    Login getById(UUID id);
 
    Login getByUsername(String username);
 
    List<Login> getAll(QueryParams queryParams);
 
    Long getCount(QueryParams queryParams);
}

DAO Implementation

JPA CriteriaQuery via Hibernate

Implementations

ContextDAOImpl.java
package de.jcpis.analysestats.persistence.hibernate;
 
import de.jcpis.analysestats.helper.ConversionHelper;
import de.jcpis.analysestats.model.QueryParams;
import de.jcpis.analysestats.model.Sorting;
import de.jcpis.analysestats.model.db.Context;
import de.jcpis.analysestats.persistence.ContextDAO;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Root;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
 
/**
 *
 * @author anthonyarents
 */
@Repository
public class ContextDAOImpl implements ContextDAO {
 
    @Autowired
    private SessionFactory sessionFactory;
 
    @Override
    public void save(final Context obj) {
        sessionFactory.getCurrentSession().saveOrUpdate(obj);
    }
 
    @Override
    public void delete(final Context obj) {
        sessionFactory.getCurrentSession().delete(obj);
    }
 
    @Override
    public Context getById(final Integer id) {
        return sessionFactory.getCurrentSession().get(Context.class, id);
    }
 
    private void initializeDependencies(final List<Context> list) {
        for (Context obj : list) {
            initializeDependencies(obj);
        }
    }
 
    private void initializeDependencies(final Context obj) {
        if (obj != null) {
            Hibernate.initialize(obj.getServer());
        }
    }
 
    private void addFilters(final CriteriaBuilder builder, final Root<Context> root, final CriteriaQuery<?> criteriaQuery, final QueryParams queryParams) {
        final String name = ConversionHelper.toString(queryParams.getFilterValue("name"));
 
        if (!StringUtils.isEmpty(name)) {
            criteriaQuery.where(builder.like(root.<String>get("name"), name + "%"));
        }
    }
 
    private void addSorting(final CriteriaBuilder builder, final Root<Context> root, final CriteriaQuery<?> criteriaQuery, final QueryParams queryParams) {
        if (queryParams.getSort().getSortings().isEmpty()) {
            criteriaQuery.orderBy(builder.asc(root.get("name")));
        } else {
            final List<Order> orderList = new ArrayList<Order>();
            for (Sorting s : queryParams.getSort().getSortings()) {
                if ("ASC".equals(s.getDirection())) {
                    orderList.add(builder.asc(root.get(s.getProperty())));
                } else {
                    orderList.add(builder.desc(root.get(s.getProperty())));
                }
            }
            criteriaQuery.orderBy(orderList);
        }
    }
 
    @Override
    public List<Context> getAll(final QueryParams queryParams) {
        final Session session = sessionFactory.getCurrentSession();
        session.enableFilter("deletedObjectsFilter");
 
        final CriteriaBuilder builder = session.getCriteriaBuilder();
        final CriteriaQuery<Context> criteriaQuery = builder.createQuery(Context.class);
        final Root<Context> root = criteriaQuery.from(Context.class);
        criteriaQuery.select(root);
 
        addFilters(builder, root, criteriaQuery, queryParams);
        addSorting(builder, root, criteriaQuery, queryParams);
 
        final TypedQuery<Context> typedQuery = session.createQuery(criteriaQuery);
        typedQuery.setFirstResult(queryParams.getStart());
        if (queryParams.isLimitted()) {
            typedQuery.setMaxResults(queryParams.getLimit());
        }
 
        return typedQuery.getResultList();
    }
 
    @Override
    public Long getCount(final QueryParams queryParams) {
        final Session session = sessionFactory.getCurrentSession();
        session.enableFilter("deletedObjectsFilter");
 
        final CriteriaBuilder builder = session.getCriteriaBuilder();
        final CriteriaQuery<Long> criteriaQuery = builder.createQuery(Long.class);
        final Root<Context> root = criteriaQuery.from(Context.class);
        criteriaQuery.select(builder.count(root));
 
        addFilters(builder, root, criteriaQuery, queryParams);
 
        final TypedQuery<Long> typedQuery = session.createQuery(criteriaQuery);
        return typedQuery.getSingleResult();
    }
 
}

*OLD* Hibernate Criteria API (legacy)

Superclass

HibernateDAO.java
package be.mentoringsystems.merke.persistence.hibernate;
 
import be.mentoringsystems.merke.model.Filter;
import be.mentoringsystems.merke.model.QueryParams;
import be.mentoringsystems.merke.model.Sorting;
import java.util.List;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.springframework.orm.hibernate5.support.HibernateDaoSupport;
 
/**
 *
 * @author anthonyarents
 */
public class HibernateDAO extends HibernateDaoSupport {
    private static final String SORT_DESC = "DESC";
 
    protected boolean addSorting(final DetachedCriteria criteria, final QueryParams queryParams) {
        if (queryParams.getSort() == null) {
            return false;
        } else {
            for (Sorting sorting : queryParams.getSort().getSortings()) {
                addOrder(criteria, sorting);
            }
            return true;
        }
    }
 
    protected void addOrder(final DetachedCriteria criteria, Sorting sorting) {
        criteria.addOrder(getOrder(sorting.getDirection(), sorting.getProperty()));
    }
 
    protected Order getOrder(final String dir, final String property) {
        if (SORT_DESC.equals(dir)) {
            return Order.desc(property);
        } else {
            return Order.asc(property);
        }
    }
 
    protected boolean addFilter(final DetachedCriteria criteria, final QueryParams queryParams) {
        if (queryParams.getFilter() == null) {
            return false;
        } else {
            for (Filter filter : queryParams.getFilter().getFilters()) {
                if (filter.getValue() == null) {
                    criteria.add(Restrictions.isNull(filter.getProperty()));
                } else {
                    criteria.add(Restrictions.eq(filter.getProperty(), filter.getValue()));
                }
            }
            return true;
        }
    }
 
    List<?> byCriteria(final DetachedCriteria criteria, final QueryParams queryParams) {
        return getHibernateTemplate().findByCriteria(criteria, queryParams.getStart(), queryParams.getLimit());
    }
 
    List<?> byCriteriaS(final DetachedCriteria criteria, final QueryParams queryParams) {
        addSorting(criteria, queryParams);
        return getHibernateTemplate().findByCriteria(criteria, queryParams.getStart(), queryParams.getLimit());
    }
 
    List<?> byCriteriaF(final DetachedCriteria criteria, final QueryParams queryParams) {
        addFilter(criteria, queryParams);
        return getHibernateTemplate().findByCriteria(criteria, queryParams.getStart(), queryParams.getLimit());
    }
 
    List<?> byCriteriaFS(final DetachedCriteria criteria, final QueryParams queryParams) {
        addFilter(criteria, queryParams);
        addSorting(criteria, queryParams);
        return getHibernateTemplate().findByCriteria(criteria, queryParams.getStart(), queryParams.getLimit());
    }
}

Hibernate DAO implementations

LoginDAOImpl.java
package be.mentoringsystems.merke.persistence.hibernate;
 
import be.mentoringsystems.merke.helper.ConversionHelper;
import be.mentoringsystems.merke.model.QueryParams;
import be.mentoringsystems.merke.model.db.Login;
import be.mentoringsystems.merke.persistence.LoginDAO;
import java.util.List;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
 
/**
 *
 * @author anthonyarents
 */
@Repository
@SuppressWarnings("unchecked")
public class LoginDAOImpl extends HibernateDAO implements LoginDAO {
 
    @Override
    public void save(final Login obj) {
        getHibernateTemplate().saveOrUpdate(obj);
    }
 
    @Override
    public Login getById(final UUID id) {
        final Login result = getHibernateTemplate().get(Login.class, id);
        return result;
    }
 
    @Override
    public Login getByUsername(final String username) {
        final DetachedCriteria criteria = DetachedCriteria.forClass(Login.class);
        criteria.add(Restrictions.eq("deleted", false));
        criteria.add(Restrictions.eq("username", username));
        List<Login> result = (List<Login>) getHibernateTemplate().findByCriteria(criteria);
        if(result.isEmpty()) {
            return null;
        } else {
            return result.get(0);
        }
    }
 
    private void addFilters(final DetachedCriteria criteria, final QueryParams queryParams) {
        final String search = ConversionHelper.toString(queryParams.getFilterValue("search"));
 
        if(!StringUtils.isEmpty(search)) {
            criteria.add(
                Restrictions.or(
                        Restrictions.ilike("firstname", search, MatchMode.ANYWHERE),
                        Restrictions.ilike("lastname", search, MatchMode.ANYWHERE),
                        Restrictions.ilike("username", search, MatchMode.ANYWHERE)
                ));
        }
    }
 
    @Override
    public List<Login> getAll(final QueryParams queryParams) {
        final DetachedCriteria criteria = DetachedCriteria.forClass(Login.class);
        criteria.add(Restrictions.eq("deleted", false));
 
        addFilters(criteria, queryParams);
        addSorting(criteria, queryParams);
 
        return (List<Login>) getHibernateTemplate().findByCriteria(criteria, queryParams.getStart(), queryParams.getLimit());
    }
 
    @Override
    public Long getCount(final QueryParams queryParams) {
        final DetachedCriteria criteria = DetachedCriteria.forClass(Login.class);
        criteria.add(Restrictions.eq("deleted", false));
 
        addFilters(criteria, queryParams);
 
        criteria.setProjection(Projections.rowCount());
        List<Long> result = (List<Long>) getHibernateTemplate().findByCriteria(criteria);
        return result.get(0);
    }
 
    @Autowired
    public void init(SessionFactory factory) {
        setSessionFactory(factory);
    }
 
}
VendorDAOImpl.java
package be.mentoringsystems.merke.persistence.hibernate;
 
import be.mentoringsystems.merke.helper.ConversionHelper;
import be.mentoringsystems.merke.model.QueryParams;
import be.mentoringsystems.merke.model.db.Vendor;
import be.mentoringsystems.merke.model.db.Vendorstall;
import be.mentoringsystems.merke.persistence.VendorDAO;
import java.util.List;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate5.HibernateTemplate;
import org.springframework.stereotype.Repository;
 
/**
 *
 * @author anthonyarents
 */
@Repository
@SuppressWarnings("unchecked")
public class VendorDAOImpl extends HibernateDAO implements VendorDAO {
 
    private void initializeDependencies(final HibernateTemplate template, final List<Vendor> list) {
        for(Vendor obj : list) {
            initializeDependencies(template, obj);
        }
    }
 
    private void initializeDependencies(final HibernateTemplate template, final Vendor obj) {
        template.initialize(obj.getVendorstalls());
        for(Vendorstall vendorstall : obj.getVendorstalls()) {
            template.initialize(vendorstall.getCertificates());
        }
        template.initialize(obj.getRemarks());
    }
 
    @Autowired
    public void init(SessionFactory factory) {
        setSessionFactory(factory);
    }
 
    @Override
    public void save(Vendor obj) {
        getHibernateTemplate().saveOrUpdate(obj);
    }
 
    @Override
    public Vendor getById(UUID id) {
        final HibernateTemplate template = getHibernateTemplate();
        final Vendor result = template.get(Vendor.class, id);
        initializeDependencies(template, result);
        return result;
    }
 
    private void addFilters(DetachedCriteria criteria, QueryParams queryParams) {
        final List<Integer> years = ConversionHelper.toIntegerList(queryParams.getFilterValue("years"));
        final List<UUID> venueIds = ConversionHelper.toUUIDList(queryParams.getFilterValue("venueIds"));
        final List<UUID> categories = ConversionHelper.toUUIDList(queryParams.getFilterValue("categoryIds"));
        final List<String> remarktypes = ConversionHelper.toStringList(queryParams.getFilterValue("remarktypes"));
        final String search = ConversionHelper.toString(queryParams.getFilterValue("search"));
 
        boolean vendorstalljoin = false;
        if(!StringUtils.isEmpty(search)) {
            criteria.add(Restrictions.or(
                Restrictions.ilike("firstname", search, MatchMode.START), 
                Restrictions.ilike("lastname", search, MatchMode.START), 
                Restrictions.ilike("companyName", search, MatchMode.START)
            ));
        }
        if(!venueIds.isEmpty()) {
            if(!vendorstalljoin) {
                criteria.createAlias("vendorstalls", "vstall");
                vendorstalljoin = true;
            }
            criteria.createAlias("vstall.venues", "ven");
            criteria.add(Restrictions.in("ven.id", venueIds));
        }
        if(!categories.isEmpty()) {
            if(!vendorstalljoin) {
                criteria.createAlias("vendorstalls", "vstall");
                vendorstalljoin = true;
            }
            criteria.createAlias("vstall.categories", "cat");
            criteria.add(Restrictions.in("cat.id", categories));
        }
        if(!remarktypes.isEmpty()) {
            criteria.createAlias("remarks", "rem");
            criteria.add(Restrictions.in("rem.category", remarktypes));
        }
        if(!years.isEmpty()) {
            final StringBuilder inBuilder = new StringBuilder("YEAR(createdOn) IN (").append(StringUtils.join(years, ",")).append(")");
            criteria.add(Restrictions.sqlRestriction(inBuilder.toString()));
        }
    }
 
    @Override
    public List<Vendor> getAll(QueryParams queryParams) {
        final HibernateTemplate template = getHibernateTemplate();
        final DetachedCriteria criteria = DetachedCriteria.forClass(Vendor.class);
        criteria.add(Restrictions.eq("deleted", false));
 
        addFilters(criteria, queryParams);
        addSorting(criteria, queryParams);
 
        final List<Vendor> result = (List<Vendor>) template.findByCriteria(criteria, queryParams.getStart(), queryParams.getLimit());
        initializeDependencies(template, result);
        return result;
    }
 
    @Override
    public Long getCount(QueryParams queryParams) {
        final DetachedCriteria criteria = DetachedCriteria.forClass(Vendor.class);
        criteria.add(Restrictions.eq("deleted", false));
 
        addFilters(criteria, queryParams);
 
        criteria.setProjection(Projections.rowCount());
        final List<Long> result = (List<Long>) getHibernateTemplate().findByCriteria(criteria);
        return result.get(0);
    }
 
}

Advanced hibernate criteria queries exist, If you need examples contact me :-).

Statistics / advanced queries with Hibernate (Legacy)

    @SuppressWarnings("unchecked")
    public Map<String, Object> getStats(Context context) {
        final HibernateTemplate template = getHibernateTemplate();
        final org.hibernate.classic.Session session = template.getSessionFactory().openSession();
        final List<Object[]> list = session.createSQLQuery(
                "select " +
                "(select count(id) from Login where contextId=:contextId and active='Y') as beraterCount, " +
                "(select count(id) from Mandant where deleted='N' and beraterId in(select beraterId from Login where contextId=:contextId and active='Y')) as mandantCount, " +
                "(select count(id) from Asset where deleted='N' and analyseId in(select id from Analyse where mandantId in(select id from Mandant where deleted='N' and beraterId in(select beraterId from Login where contextId=:contextId and active='Y')))) as assetCount, " +
                "(select count(id) from RequestLog where logtype='mobile request' and beraterId in(select beraterId from Login where contextId=:contextId and active='Y')) as syncCount"
        ).setParameter("contextId", context.getId().toString()).list();
 
        Object[] objs = list.get(0);
        Map<String, Object> result = new HashMap<String, Object>();
        result.put("beraterCount", objs[0]);
        result.put("mandantCount", objs[1]);
        result.put("assetCount", objs[2]);
        result.put("syncCount", objs[3]);
 
        final List<Object[]> list2 = session.createSQLQuery("select YEAR(createdOn), MONTH(createdOn), COUNT(id) from Mandant where deleted='N' and beraterId in(select beraterId from Login where contextId=:contextId and active='Y') group by YEAR(createdOn) ASC, MONTH(createdOn) ASC").setParameter("contextId", context.getId().toString()).list();
 
        List<Map<String, Object>> mandantStats = new ArrayList<Map<String, Object>>();
        for(Object[] objs2 : list2) {
            Map<String, Object> mandantstat = new HashMap<String, Object>();
            mandantstat.put("year", objs2[0]);
            mandantstat.put("month", objs2[1]);
            mandantstat.put("count", objs2[2]);
            mandantStats.add(mandantstat);
        }
        result.put("mandantStats", mandantStats);
 
        return result;
    }

Service Interface

Of note : extends UserDetailsService (allows for custom spring security login)

LoginService.java
package be.mentoringsystems.merke.service;
 
import be.mentoringsystems.merke.model.QueryParams;
import be.mentoringsystems.merke.model.db.Login;
import be.mentoringsystems.merke.model.dto.LoginDTO;
import be.mentoringsystems.merke.model.dto.PagedListDTO;
import java.util.List;
import java.util.UUID;
import org.springframework.security.core.userdetails.UserDetailsService;
 
/**
 *
 * @author anthonyarents
 */
public interface LoginService extends UserDetailsService {
 
    void save(Login obj);
 
    Login save(LoginDTO dto);
 
    void delete(Login obj);
 
    void delete(UUID id);
 
    Login getById(UUID id);
 
    Login getByUsername(String login);
 
    List<Login> getAll(QueryParams queryParams);
 
    Long getCount(QueryParams queryParams);
 
    PagedListDTO getAllDTO(QueryParams queryParams);
 
    Login getCurrentLogin();
 
    Login getSourceLogin();
 
    void changePassword(String password);
 
    void changePassword(Login login, String password);
 
    Login getByUsernameAndPassword(String username, String password);
}
VendorService.java
package be.mentoringsystems.merke.service;
 
import be.mentoringsystems.merke.model.QueryParams;
import be.mentoringsystems.merke.model.db.Vendor;
import be.mentoringsystems.merke.model.dto.PagedListDTO;
import be.mentoringsystems.merke.model.dto.VendorDTO;
import java.util.List;
import java.util.UUID;
 
/**
 *
 * @author anthonyarents
 */
public interface VendorService {
 
    void save(Vendor obj);
 
    Vendor save(VendorDTO dto);
 
    void delete(Vendor obj);
 
    void delete(UUID id);
 
    Vendor getById(UUID id);
 
    Vendor getByLoginId(UUID loginId);
 
    List<Vendor> getAll(QueryParams queryParams);
 
    Long getCount(QueryParams queryParams);
 
    PagedListDTO getAllDTO(QueryParams queryParams);
 
    boolean addMunicipality(UUID vendorId, UUID municipalityId);
 
    boolean removeMunicipality(UUID vendorId, UUID municipalityId);
}

Service Implementation

LoginServiceImpl.java
package be.mentoringsystems.merke.service.impl;
 
import be.mentoringsystems.merke.helper.DTOHelper;
import be.mentoringsystems.merke.model.QueryParams;
import be.mentoringsystems.merke.model.db.Login;
import be.mentoringsystems.merke.model.db.Vendor;
import be.mentoringsystems.merke.model.dto.LoginDTO;
import be.mentoringsystems.merke.model.dto.PagedListDTO;
import be.mentoringsystems.merke.persistence.LoginDAO;
import be.mentoringsystems.merke.service.ContextService;
import be.mentoringsystems.merke.service.LoginService;
import be.mentoringsystems.merke.service.VendorService;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
 
/**
 *
 * @author anthonyarents
 */
@Service
@Transactional(propagation = Propagation.SUPPORTS)
public class LoginServiceImpl implements LoginService {
 
    @Autowired
    private transient LoginDAO loginDAO;
    @Autowired
    private transient ContextService contextService;
    @Autowired
    private transient VendorService vendorService;
    @Autowired
    private transient PasswordEncoder passwordEncoder;
    private static final Logger LOGGER = LogManager.getLogger(LoginServiceImpl.class);
 
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void save(final Login login) {
        loginDAO.save(login);
    }
 
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public Login save(final LoginDTO dto) {
        final Login login = this.getCurrentLogin();
        Login obj;
        boolean createVendor = false;
        if (dto.getId() == null) {
            obj = new Login();
            createVendor = true;
            LOGGER.info("Creating a new Login");
        } else {
            obj = getById(dto.getId());
            LOGGER.info("Updating existing Login({})", dto.getId());
            LOGGER.info("ContextId for this login:" + dto.getContextId());
 
            if (login.getContextId() != null && !login.getContextId().equals(obj.getContextId()) && !login.isMSAdmin()) {
                obj = null;
                LOGGER.info("Access not allowed");
            }
        }
 
        if (obj != null) {
            // save password if it exists
            String dtoPassword = dto.getPassword();
            dto.setPassword(null);
            // copy non null
            DTOHelper.copyNonNull(dto, obj);
            // if password was not null :
            if (StringUtils.hasText(dtoPassword)) {
                obj.setPassword(encryptPassword(dtoPassword));
            }
 
            if (dto.getContextId() != null) {
                obj.setContextId(dto.getContextId());
                obj.setContext(contextService.getById(dto.getContextId()));
            } else {
                //Add context of current login
                if (login != null && login.getContextId() != null) {
                    obj.setContextId(login.getContextId());
                    obj.setContext(contextService.getById(obj.getContextId()));
                }
            }
            LOGGER.info("Context before saving:" + obj.getContextId());
            save(obj);
            LOGGER.info("Login({}) saved", obj.getId() + " context: " + obj.getContextId());
            if (createVendor && obj.getGroup() == 2) {
                LOGGER.info("Creating a new Vendor");
                final Vendor vendor = new Vendor();
                vendor.setFirstname(obj.getFirstname());
                vendor.setLastname(obj.getLastname());
                vendor.setEmail(obj.getEmail());
                vendor.setLoginId(obj.getId());
                vendor.setFlag(0);
                vendor.setActive(true);
                vendorService.save(vendor);
                LOGGER.info("Vendor({}) saved", vendor.getId());
            } else if (obj.getGroup() == 2) {
                LOGGER.info("Updating Vendor");
                Vendor vendor = vendorService.getByLoginId(obj.getId());
                vendor.setFirstname(obj.getFirstname());
                vendor.setLastname(obj.getLastname());
                vendor.setEmail(obj.getEmail());
                vendorService.save(vendor);
                LOGGER.info("Vendor({}) saved", vendor.getId());
            }
        }
 
        return obj;
    }
 
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void delete(final Login login) {
        login.setDeleted(true);
        save(login);
        LOGGER.info("Login({}) deleted", login.getId());
    }
 
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void delete(final UUID id) {
        final Login login = getById(id);
        delete(login);
    }
 
    @Override
    @Transactional(readOnly = true)
    public Login getById(final UUID id) {
        return loginDAO.getById(id);
    }
 
    @Override
    @Transactional(readOnly = true)
    public Login getByUsername(final String username) {
        return loginDAO.getByUsername(username);
    }
 
    @Override
    @Transactional(readOnly = true)
    public List<Login> getAll(final QueryParams queryParams) {
        return loginDAO.getAll(queryParams);
    }
 
    @Override
    @Transactional(readOnly = true)
    public Long getCount(final QueryParams queryParams) {
        return loginDAO.getCount(queryParams);
    }
 
    @Override
    @Transactional(readOnly = true)
    public PagedListDTO getAllDTO(final QueryParams queryParams) {
        final Login login = this.getCurrentLogin();
        if (login.getContextId() != null && !login.isMSAdmin()) {
            queryParams.addFilter("contextId", login.getContextId());
        }
 
        final PagedListDTO pagedListDTO = new PagedListDTO();
        pagedListDTO.setData(getAll(queryParams));
        pagedListDTO.setTotal(getCount(queryParams));
        return pagedListDTO;
    }
 
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void changePassword(final String password) {
        final Login login = getCurrentLogin();
        changePassword(login, password);
    }
 
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void changePassword(final Login login, final String password) {
        login.setPassword(encryptPassword(password));
        save(login);
        LOGGER.info("Login({}) password changed", login.getId());
    }
 
    public String encryptPassword(final String password) {
        return passwordEncoder.encode(password);
    }
 
    @Override
    @Transactional(readOnly = true)
    public UserDetails loadUserByUsername(final String loginName) throws UsernameNotFoundException {
        final Login login = getByUsername(loginName);
 
        if (login == null) {
            throw new UsernameNotFoundException(loginName);
        } else {
            login.setAuthorities(getAuthorities(login));
            return login;
        }
    }
 
    public List<GrantedAuthority> getAuthorities(final Login login) {
        final List<GrantedAuthority> authorities = new ArrayList<>();
        if (login.isMSAdmin()) {
            authorities.add(new SimpleGrantedAuthority("ROLE_MSADMIN"));
            authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        }
        if (login.isAdmin()) {
            authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        }
        if (login.isFireman()) {
            authorities.add(new SimpleGrantedAuthority("ROLE_FIREMAN"));
        }
        authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
        return authorities;
    }
 
    @Override
    public Login getCurrentLogin() {
        if (SecurityContextHolder.getContext() != null && SecurityContextHolder.getContext().getAuthentication() != null) {
            Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            if (principal instanceof Login) {
                return (Login) principal;
            }
        }
        return null;
    }
 
    @Override
    public Login getSourceLogin() {
        final Login current = getCurrentLogin();
        Login original = null;
 
        for (GrantedAuthority auth : current.getAuthorities()) {
            if (auth instanceof SwitchUserGrantedAuthority) {
                Authentication authSource = ((SwitchUserGrantedAuthority) auth).getSource();
                if (authSource instanceof Login) {
                    original = (Login) authSource;
                }
            }
        }
 
        return original;
    }
 
    @Override
    @Transactional(readOnly = true)
    public Login getByUsernameAndPassword(final String username, final String password) {
        final Login login = getByUsername(username);
        if (login == null) {
            return null;
        } else {
            if (passwordEncoder.matches(password, login.getPassword())) {
                return login;
            } else {
                return null;
            }
        }
    }
}
VendorServiceImpl.java
package be.mentoringsystems.merke.service.impl;
 
import be.mentoringsystems.merke.helper.DTOHelper;
import be.mentoringsystems.merke.model.QueryParams;
import be.mentoringsystems.merke.model.db.Context;
import be.mentoringsystems.merke.model.db.Login;
import be.mentoringsystems.merke.model.db.Municipality;
import be.mentoringsystems.merke.model.db.Vendor;
import be.mentoringsystems.merke.model.db.Vendorstall;
import be.mentoringsystems.merke.model.dto.PagedListDTO;
import be.mentoringsystems.merke.model.dto.VendorDTO;
import be.mentoringsystems.merke.persistence.VendorDAO;
import be.mentoringsystems.merke.service.ContextService;
import be.mentoringsystems.merke.service.LoginService;
import be.mentoringsystems.merke.service.MunicipalityService;
import be.mentoringsystems.merke.service.VendorService;
import be.mentoringsystems.merke.service.VendorstallService;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
 
/**
 *
 * @author anthonyarents
 */
@Service
@Transactional(propagation = Propagation.SUPPORTS)
public class VendorServiceImpl implements VendorService {
 
    @Autowired
    private transient VendorDAO vendorDAO;
    @Autowired
    private transient LoginService loginService;
    @Autowired
    private transient VendorstallService vendorstallService;
    @Autowired
    private transient MunicipalityService municipalityService;
    @Autowired
    private transient ContextService contextService;
    private static final Logger LOGGER = LogManager.getLogger(VendorServiceImpl.class);
 
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void save(final Vendor obj) {
        vendorDAO.save(obj);
    }
 
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public Vendor save(final VendorDTO dto) {
        Vendor obj;
        if (dto.getId() == null) {
            obj = new Vendor();
            LOGGER.info("Creating a new Vendor");
        } else {
            if (dto.getMunicipalityIds() == null) {
                obj = getById(dto.getId());
            } else {
                obj = vendorDAO.getByIdWithMunicipalities(dto.getId());
            }
            LOGGER.info("Updating existing Vendor({})", dto.getId());
        }
 
        if (obj != null) {
            DTOHelper.copyNonNull(dto, obj);
 
            if (dto.getMunicipalityIds() != null) {
                List<Municipality> lm = new ArrayList<>();
                List<Municipality> municipalitiesWithContext = new ArrayList<>();
                List<Municipality> municipalitiesWithoutContext = new ArrayList<>();
                List<Context> subscribedContexts = obj.getContexts();
 
                for (UUID mid : dto.getMunicipalityIds()) {
                    lm.add(municipalityService.getById(mid));
                }
                obj.getMunicipalities().retainAll(lm);
                lm.removeAll(obj.getMunicipalities());
                if (!lm.isEmpty()) {
                    lm.forEach((newlyAddedMunicipality) -> {
                        if (newlyAddedMunicipality.getContextId() != null) {
                            municipalitiesWithContext.add(newlyAddedMunicipality);
                        } else {
                            municipalitiesWithoutContext.add(newlyAddedMunicipality);
                        }
                    });
                    obj.getMunicipalities().addAll(lm);
                }
 
                if (!municipalitiesWithContext.isEmpty()) {
                    municipalitiesWithContext.forEach((municipality) -> {
                        Context cityInMerke = contextService.getById(municipality.getContextId());
                        if (!subscribedContexts.contains(cityInMerke)) {
                            //TODO : ask user for confirmation first!
                            //If user says no, add to seperate list so we prevent asking the same thing twice?
                            subscribedContexts.add(cityInMerke);
 
                        }
 
                    });
                }
 
                if (!municipalitiesWithoutContext.isEmpty()) {
                    municipalitiesWithoutContext.forEach((municipality) -> {
                        //TODO : send message to user which municipalities they want to subscribe to, then save those in a seperate list?
 
                    });
                }
 
            }
 
            //obj.setActive(true);
            save(obj);
            LOGGER.info("Vendor({}) saved", obj.getId());
 
            LOGGER.info("Updating Login");
            final Login login = loginService.getById(obj.getLoginId());
            login.setEmail(obj.getEmail());
            login.setFirstname(obj.getFirstname());
            login.setLastname(obj.getLastname());
            loginService.save(login);
            LOGGER.info("Login({}) saved", login.getId());
        }
 
        return obj;
    }
 
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void delete(final Vendor obj) {
        final QueryParams params = new QueryParams();
        params.addFilter("vendorId", obj.getId());
        List<Vendorstall> stalls = vendorstallService.getAll(params);
        for (Vendorstall stall : stalls) {
            vendorstallService.delete(stall);
        }
 
        obj.setDeleted(true);
        save(obj);
        LOGGER.info("Vendor({}) deleted", obj.getId());
        loginService.delete(obj.getLoginId());
    }
 
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void delete(final UUID id) {
        final Vendor obj = getById(id);
        delete(obj);
    }
 
    @Override
    @Transactional(readOnly = true)
    public Vendor getById(final UUID id) {
        return vendorDAO.getById(id);
    }
 
    @Override
    @Transactional(readOnly = true)
    public Vendor getByLoginId(final UUID loginId) {
        QueryParams queryParams = new QueryParams();
        queryParams.addFilter("loginId", loginId);
        List<Vendor> vendors = getAll(queryParams);
        if (vendors.isEmpty()) {
            return null;
        } else {
            return vendors.get(0);
        }
    }
 
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public boolean addMunicipality(final UUID vendorId, final UUID municipalityId) {
        final Vendor vendor = getById(vendorId);
        final Municipality municipality = municipalityService.getById(municipalityId);
        if (!vendor.getMunicipalities().contains(municipality)) {
            vendor.getMunicipalities().add(municipality);
            save(vendor);
        }
        return true;
    }
 
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public boolean removeMunicipality(final UUID vendorId, final UUID municipalityId) {
        final Vendor vendor = getById(vendorId);
        final Municipality municipality = municipalityService.getById(municipalityId);
        if (vendor.getMunicipalities().contains(municipality)) {
            vendor.getMunicipalities().remove(municipality);
            save(vendor);
        }
        return true;
    }
 
    @Override
    @Transactional(readOnly = true)
    public List<Vendor> getAll(final QueryParams queryParams) {
        return vendorDAO.getAll(queryParams);
    }
 
    @Override
    @Transactional(readOnly = true)
    public Long getCount(final QueryParams queryParams) {
        return vendorDAO.getCount(queryParams);
    }
 
    @Override
    @Transactional(readOnly = true)
    public PagedListDTO getAllDTO(final QueryParams queryParams) {
        queryParams.addFilter("contextId", loginService.getCurrentLogin().getContextId());
        final PagedListDTO result = new PagedListDTO();
        result.setData(getAll(queryParams));
        result.setTotal(getCount(queryParams));
        return result;
    }
}

View

Jsonview values, notice we'll be using public as a default.

View.java
package be.mentoringsystems.merke.model.dto;
 
/**
 *
 * @author anthonyarents
 */
public class View {
 
    public interface Public {
    };
 
    public interface Vendor extends Public {
    };
 
    public interface Login extends Public {
    };
 
    public interface Venue extends Public {
    };
 
    public interface Certificate extends Public {
    };
 
    public interface VenueEvent extends Public {
    };
 
    public interface VendorstallPlacement extends Public {
    };
}

Data Transfer Objects (DTO)

We're using DTO objects to have more control of incoming data.

LoginDTO.java
package be.mentoringsystems.merke.model.dto;
 
import java.util.UUID;
 
/**
 *
 * @author anthonyarents
 */
public class LoginDTO {
 
    private UUID id;
    private String firstname;
    private String lastname;
    private String email;
    private String password;
    private Integer group;
    private String username;
    private Boolean active;
    private String language;
    private Boolean fairvendor;
    private Boolean marketvendor;
    private UUID contextId;
    private UUID pictureId;
 
    public UUID getPictureId() {
        return pictureId;
    }
 
    public void setPictureId(final UUID pictureId) {
        this.pictureId = pictureId;
    }
 
    public UUID getContextId() {
        return contextId;
    }
 
    public void setContextId(final UUID contextId) {
        this.contextId = contextId;
    }
 
    public Boolean getFairvendor() {
        return fairvendor;
    }
 
    public void setFairvendor(final Boolean fairvendor) {
        this.fairvendor = fairvendor;
    }
 
    public Boolean getMarketvendor() {
        return marketvendor;
    }
 
    public void setMarketvendor(final Boolean marketvendor) {
        this.marketvendor = marketvendor;
    }
 
    public String getLanguage() {
        return language;
    }
 
    public void setLanguage(final String language) {
        this.language = language;
    }
 
    public Boolean getActive() {
        return active;
    }
 
    public void setActive(final Boolean active) {
        this.active = active;
    }
 
    public UUID getId() {
        return id;
    }
 
    public void setId(final UUID id) {
        this.id = id;
    }
 
    public String getEmail() {
        return email;
    }
 
    public void setEmail(final String email) {
        this.email = email;
    }
 
    public String getFirstname() {
        return firstname;
    }
 
    public void setFirstname(final String firstname) {
        this.firstname = firstname;
    }
 
    public Integer getGroup() {
        return group;
    }
 
    public void setGroup(final Integer group) {
        this.group = group;
    }
 
    public String getLastname() {
        return lastname;
    }
 
    public void setLastname(final String lastname) {
        this.lastname = lastname;
    }
 
    public String getUsername() {
        return username;
    }
 
    public void setUsername(final String username) {
        this.username = username;
    }
 
    public String getPassword() {
        return password;
    }
 
    public void setPassword(final String password) {
        this.password = password;
    }
}
VendorDTO.java
package be.mentoringsystems.merke.model.dto;
 
import be.mentoringsystems.merke.model.db.Context;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
import org.springframework.format.annotation.DateTimeFormat;
 
/**
 *
 * @author anthonyarents
 */
public class VendorDTO {
 
    private UUID id;
    private UUID parentid;
    private UUID loginId;
    private String title;
    private String title2;
    private String accounttype;
    private String firstname;
    private String lastname;
    private String idnrNationalRegister;
    private String street;
    private String number;
    private String mailboxNumber;
    private String postalcode;
    private String city;
    private String companyName;
    private String companyNumber;
    private String bankaccount;
    private String telephonenumber;
    private String email;
    private String mobilenumber;
    private String mobilenumber2;
    private LocalDate birthdate;
    private String permitNumber;
    private String status;
    private String change;
    private Integer flag;
    private String flagnote;
    private List<Context> contexts;
    private List<UUID> municipalityIds;
    private UUID contextId;
 
    public String getAccounttype() {
        return accounttype;
    }
 
    public void setAccounttype(final String accounttype) {
        this.accounttype = accounttype;
    }
 
    public UUID getContextId() {
        return contextId;
    }
 
    public void setContextId(final UUID contextId) {
        this.contextId = contextId;
    }
 
    public List<UUID> getMunicipalityIds() {
        return municipalityIds;
    }
 
    public void setMunicipalityIds(final List<UUID> municipalityIds) {
        this.municipalityIds = municipalityIds;
    }
 
    public List<Context> getContexts() {
        return contexts;
    }
 
    public void setContexts(final List<Context> contexts) {
        this.contexts = contexts;
    }
 
    public Integer getFlag() {
        return flag;
    }
 
    public void setFlag(final Integer flag) {
        this.flag = flag;
    }
 
    public String getFlagnote() {
        return flagnote;
    }
 
    public void setFlagnote(final String flagnote) {
        this.flagnote = flagnote;
    }
 
    public String getTitle2() {
        return title2;
    }
 
    public void setTitle2(final String title2) {
        this.title2 = title2;
    }
 
    public String getChange() {
        return change;
    }
 
    public void setChange(final String change) {
        this.change = change;
    }
 
    public String getStatus() {
        return status;
    }
 
    public void setStatus(final String status) {
        this.status = status;
    }
 
    public String getPermitNumber() {
        return permitNumber;
    }
 
    public void setPermitNumber(final String permitNumber) {
        this.permitNumber = permitNumber;
    }
 
    public UUID getId() {
        return id;
    }
 
    public void setId(final UUID id) {
        this.id = id;
    }
 
    public UUID getLoginId() {
        return loginId;
    }
 
    public void setLoginId(final UUID loginId) {
        this.loginId = loginId;
    }
 
    public UUID getParentid() {
        return parentid;
    }
 
    public void setParentid(final UUID parentid) {
        this.parentid = parentid;
    }
 
    public String getTitle() {
        return title;
    }
 
    public void setTitle(final String title) {
        this.title = title;
    }
 
    public String getFirstname() {
        return firstname;
    }
 
    public void setFirstname(final String firstname) {
        this.firstname = firstname;
    }
 
    public String getLastname() {
        return lastname;
    }
 
    public void setLastname(final String lastname) {
        this.lastname = lastname;
    }
 
    public String getIdnrNationalRegister() {
        return idnrNationalRegister;
    }
 
    public void setIdnrNationalRegister(final String idnrNationalRegister) {
        this.idnrNationalRegister = idnrNationalRegister;
    }
 
    public String getStreet() {
        return street;
    }
 
    public void setStreet(final String street) {
        this.street = street;
    }
 
    public String getNumber() {
        return number;
    }
 
    public void setNumber(final String number) {
        this.number = number;
    }
 
    public String getMailboxNumber() {
        return mailboxNumber;
    }
 
    public void setMailboxNumber(final String mailboxNumber) {
        this.mailboxNumber = mailboxNumber;
    }
 
    public String getPostalcode() {
        return postalcode;
    }
 
    public void setPostalcode(final String postalcode) {
        this.postalcode = postalcode;
    }
 
    public String getCity() {
        return city;
    }
 
    public void setCity(final String city) {
        this.city = city;
    }
 
    public String getCompanyName() {
        return companyName;
    }
 
    public void setCompanyName(final String companyName) {
        this.companyName = companyName;
    }
 
    public String getCompanyNumber() {
        return companyNumber;
    }
 
    public void setCompanyNumber(final String companyNumber) {
        this.companyNumber = companyNumber;
    }
 
    public String getBankaccount() {
        return bankaccount;
    }
 
    public void setBankaccount(final String bankaccount) {
        this.bankaccount = bankaccount;
    }
 
    public String getTelephonenumber() {
        return telephonenumber;
    }
 
    public void setTelephonenumber(final String telephonenumber) {
        this.telephonenumber = telephonenumber;
    }
 
    public String getEmail() {
        return email;
    }
 
    public void setEmail(final String email) {
        this.email = email;
    }
 
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    public LocalDate getBirthdate() {
        return birthdate;
    }
 
    public void setBirthdate(final LocalDate birthdate) {
        this.birthdate = birthdate;
    }
 
    public String getMobilenumber() {
        return mobilenumber;
    }
 
    public void setMobilenumber(final String mobilenumber) {
        this.mobilenumber = mobilenumber;
    }
 
    public String getMobilenumber2() {
        return mobilenumber2;
    }
 
    public void setMobilenumber2(final String mobilenumber2) {
        this.mobilenumber2 = mobilenumber2;
    }
}

It's also useful for outgoing data :

ApiDTO.java
package be.mentoringsystems.merke.model.dto;
 
import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.ArrayList;
import java.util.List;
 
/**
 *
 * @author anthonyarents
 */
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ApiDTO {
 
    private Boolean success = true;
    private Object data;
    private List<String> messages = new ArrayList<String>();
 
    public Object getData() {
        return data;
    }
 
    public void setData(final Object data) {
        this.data = data;
    }
 
    public List<String> getMessages() {
        return messages;
    }
 
    public void setMessages(final List<String> messages) {
        this.messages = messages;
    }
 
    public void addMessage(final String message) {
        this.messages.add(message);
    }
 
    public Boolean getSuccess() {
        return success;
    }
 
    public void setSuccess(final Boolean success) {
        this.success = success;
    }
 
    public ApiDTO(final Object data) {
        this.data = data;
        if (data == null) {
            this.success = false;
        }
    }
 
    public ApiDTO() {
    }
 
    public static ApiDTO error() {
        final ApiDTO apiDto = new ApiDTO();
        apiDto.setSuccess(Boolean.FALSE);
        return apiDto;
    }
 
    public static ApiDTO error(final List<String> messages) {
        final ApiDTO apiDto = new ApiDTO();
        apiDto.setSuccess(Boolean.FALSE);
        apiDto.setMessages(messages);
        return apiDto;
    }
}

and paging :

PagedListDTO.java
package be.mentoringsystems.merke.model.dto;
 
import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.List;
 
/**
 *
 * @author anthonyarents
 */
@JsonInclude(JsonInclude.Include.NON_NULL)
public class PagedListDTO {
 
    private List<?> data;
    private Long total;
    private Boolean success = true;
 
    public List<?> getData() {
        return data;
    }
 
    public void setData(final List<?> data) {
        this.data = data;
    }
 
    public Boolean getSuccess() {
        return success;
    }
 
    public void setSuccess(final Boolean success) {
        this.success = success;
    }
 
    public Long getTotal() {
        return total;
    }
 
    public void setTotal(final Long total) {
        this.total = total;
    }
 
    public void setTotal(final int total) {
        this.setTotal(Long.valueOf(total));
    }
 
}

*OLD* Presentation (Ajax, MultiActionController)

use requestmapping to map based on action parameter

BankverbindungController.java
package de.jcpis.analyse.presentation;
 
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
 
/**
 *
 * @author anthonyarents
 */
@Controller
@RequestMapping("/bankverbindung.json")
public class BankverbindungController {
 
@RequestMapping(params = "action=validateBankverbindung")
    public ModelAndView validateBankverbindung(final WebRequest hsr) {
    }
    }

Presentation (Restcontroller)

Of note :

  • Do not use DAO classes here!
  • @PreAuthorize, adds a little security (role based here, still blind to ACL)
  • @JsonView,

I recommend adding it default : ”@JsonView(View.Public.class)” to every method which returns a model.db or packages a model.db.
The benefit for you : you'll be able to choose the view used here : View.Public = all except for the fields marked with @JsonView extending View.Public.

LoginController.java
package be.mentoringsystems.merke.presentation.rest;
 
import be.mentoringsystems.merke.model.QueryParams;
import be.mentoringsystems.merke.model.db.Login;
import be.mentoringsystems.merke.model.dto.ApiDTO;
import be.mentoringsystems.merke.model.dto.LoginDTO;
import be.mentoringsystems.merke.model.dto.PagedListDTO;
import be.mentoringsystems.merke.model.dto.View;
import be.mentoringsystems.merke.security.JWSHelper;
import be.mentoringsystems.merke.service.LoginService;
import com.fasterxml.jackson.annotation.JsonView;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
 
/**
 *
 * @author anthonyarents
 */
@RestController
@RequestMapping("/logins")
public class LoginController {
 
    @Autowired
    private LoginService loginService;
 
    @RequestMapping(value = "/authenticate", method = RequestMethod.POST)
    public ApiDTO authenticate(@RequestParam(required = false) final String username, @RequestParam(required = false) final String password, final HttpServletRequest hsr) {
        final ApiDTO dto = ApiDTO.error();
 
        String session = JWSHelper.getJwsSubject(hsr);
        Login login = null;
        if (session == null) {
            login = loginService.getCurrentLogin();
        } else {
            login = loginService.getByUsername(session);
        }
        if (login == null) {
            login = loginService.getByUsernameAndPassword(username, password);
        }
 
        if (login != null) {
            String jws = JWSHelper.generateJwsWithSubject(login.getUsername());
            login.setJwt(jws);
            dto.setData(login);
            dto.setSuccess(Boolean.TRUE);
        }
 
        return dto;
    }
 
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    @ResponseStatus(HttpStatus.OK)
    public ApiDTO deleteById(@PathVariable final UUID id) {
        loginService.delete(id);
        return new ApiDTO();
    }
 
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @JsonView(View.Login.class)
    @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
    @ResponseStatus(HttpStatus.OK)
    public ApiDTO updateById(@PathVariable final UUID id, @RequestBody @Validated final LoginDTO dto, final BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return ApiDTO.error();
        }
        dto.setId(id);
        final Login result = loginService.save(dto);
        return new ApiDTO(result);
    }
 
    @PreAuthorize("@methodSecurity.hasLoginAccess(#id)")
    @JsonView(View.Login.class)
    @RequestMapping(value = "/gdprCheck", method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.OK)
    public ApiDTO gdprCheck(@RequestParam final UUID id) {
        Login login = loginService.getCurrentLogin();
        if (login != null && login.getId().equals(id)) {
            login.setGdprCheck(true);
            loginService.save(login);
            return new ApiDTO();
        } else {
            return ApiDTO.error();
        }
    }
 
    @PreAuthorize("@methodSecurity.hasLoginAccess(#id)")
    @JsonView(View.Login.class)
    @RequestMapping(value = "/updatepicture", method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.OK)
    public ApiDTO updatepicture(@RequestParam final UUID id, @RequestParam final UUID pictureId) {
        Login login = loginService.getCurrentLogin();
        if (login.getId().equals(id)) {
            login.setPictureId(pictureId);
            loginService.save(login);
        } else {
            LoginDTO dto = new LoginDTO();
            dto.setId(id);
            dto.setPictureId(pictureId);
            login = loginService.save(dto);
        }
        return new ApiDTO(login);
    }
 
    @JsonView(View.Login.class)
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @RequestMapping(value = "", method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.CREATED)
    public ApiDTO createNew(@RequestBody @Validated final LoginDTO dto, final BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return ApiDTO.error();
        }
        final Login result = loginService.save(dto);
        return new ApiDTO(result);
    }
 
    @JsonView(View.Login.class)
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public ApiDTO getById(@PathVariable final UUID id) {
        final Login result = loginService.getById(id);
        return new ApiDTO(result);
    }
 
    @JsonView(View.Login.class)
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @RequestMapping(value = "", method = RequestMethod.GET)
    public PagedListDTO getAll(final QueryParams queryParams) {
        return loginService.getAllDTO(queryParams);
    }
}
VendorController.java
package be.mentoringsystems.merke.presentation.rest;
 
import be.mentoringsystems.merke.model.QueryParams;
import be.mentoringsystems.merke.model.db.Vendor;
import be.mentoringsystems.merke.model.dto.ApiDTO;
import be.mentoringsystems.merke.model.dto.PagedListDTO;
import be.mentoringsystems.merke.model.dto.VendorDTO;
import be.mentoringsystems.merke.model.dto.View;
import be.mentoringsystems.merke.service.VendorService;
import com.fasterxml.jackson.annotation.JsonView;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
 
/**
 *
 * @author anthonyarents
 */
@RestController
@RequestMapping("/vendors")
public class VendorController {
 
    @Autowired
    private transient VendorService vendorService;
 
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    @ResponseStatus(HttpStatus.OK)
    public ApiDTO deleteById(@PathVariable UUID id) {
        vendorService.delete(id);
        return new ApiDTO();
    }
 
    @JsonView(View.Vendor.class)
    @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
    @ResponseStatus(HttpStatus.OK)
    public ApiDTO updateById(@PathVariable UUID id, @RequestBody VendorDTO dto) {
        dto.setId(id);
        final Vendor result = vendorService.save(dto);
        return new ApiDTO(result);
    }
 
    @JsonView(View.Vendor.class)
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @RequestMapping(value = "", method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.CREATED)
    public ApiDTO createNew(@RequestBody VendorDTO dto) {
        final Vendor result = vendorService.save(dto);
        return new ApiDTO(result);
    }
 
    @JsonView(View.Vendor.class)
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public ApiDTO getById(@PathVariable UUID id) {
        final Vendor result = vendorService.getById(id);
        return new ApiDTO(result);
    }
 
    @JsonView(View.Vendor.class)
    @RequestMapping(value = "", method = RequestMethod.GET)
    public PagedListDTO getAll(QueryParams queryParams) {
        return vendorService.getAllDTO(queryParams);
    }
}

Presentation (Advice)

InitBinder is stateful, this method is called for EVERY @RequestParam, SimpleDateFormat which is not threadsafe won't be a problem here.

By setting a value for the InitBinder annotation, we can limit the usage to a specific variable.

Advice.java
package be.mentoringsystems.merke.presentation;
 
import be.mentoringsystems.merke.model.FilterMap;
import be.mentoringsystems.merke.model.SortingMap;
import be.mentoringsystems.merke.model.dto.ApiDTO;
import be.mentoringsystems.merke.presentation.conversion.FilterMapEditor;
import be.mentoringsystems.merke.presentation.conversion.SortingMapEditor;
import java.util.ArrayList;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
 
/**
 *
 * @author anthonyarents
 */
@ControllerAdvice(annotations = RestController.class)
public class Advice {
 
    private static final Logger LOGGER = LogManager.getLogger(Advice.class);
 
    @InitBinder("queryParams")
    public void initBinderForQueryParams(final WebDataBinder binder) {
        binder.registerCustomEditor(FilterMap.class, new FilterMapEditor());
        binder.registerCustomEditor(SortingMap.class, new SortingMapEditor());
    }
 
    @ExceptionHandler(value = AccessDeniedException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ResponseBody
    public ApiDTO accessDeniedException(final AccessDeniedException exception, final WebRequest request) {
        LOGGER.error(exception.getMessage());
        final List<String> messageList = new ArrayList<String>();
        messageList.add(exception.getMessage());
        return ApiDTO.error(messageList);
        //return ApiDTO.error(Throwables.toStringList(exception));
    }
 
    @ExceptionHandler(value = Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public ApiDTO exception(final Exception exception, final WebRequest request) {
        final List<String> messageList = new ArrayList<String>();
        if ("org.apache.catalina.connector.ClientAbortException".equals(exception.getClass().getCanonicalName())) {
            LOGGER.debug(exception.getMessage());
            //LOGGER.info("ignored client abort");
            return null;
        } else {
            LOGGER.error(exception.getMessage(), exception);
            messageList.add(exception.getMessage());
        }
        return ApiDTO.error(messageList);
    }
}

Multiple Dateformat support

When you need to support reading 2 dateformats

MultipleDateFormat.java
package be.mentoringsystems.paypermail.presentation;
 
import java.text.DateFormat;
import java.text.FieldPosition;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
 
/**
 *
 * @author anthonyarents
 */
public class MultipleDateFormat extends DateFormat {
 
    private List<DateFormat> formats = new ArrayList<DateFormat>();
    private static final Logger LOGGER = LogManager.getLogger(MultipleDateFormat.class);
 
    public void addFormat(final DateFormat format) {
        formats.add(format);
    }
 
    @Override
    public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }
 
    @Override
    public Date parse(String source, ParsePosition pos) {
        Date result = null;
        for (DateFormat format : formats) {
            try {
                result = format.parse(source, pos);
            } catch (Exception ex) {
                LOGGER.error(ex.getMessage(), ex);
            }
            if (result != null) {
                break;
            }
        }
        return result;
    }
 
}

Presentation (Conversion)

FilterMapEditor.java
package be.mentoringsystems.merke.presentation.conversion;
 
import be.mentoringsystems.merke.model.Filter;
import be.mentoringsystems.merke.model.FilterMap;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import java.beans.PropertyEditorSupport;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
 
/**
 *
 * @author anthonyarents
 */
public class FilterMapEditor extends PropertyEditorSupport {
 
    private static final Logger LOGGER = LogManager.getLogger(FilterMapEditor.class);
 
    @Override
    public void setAsText(final String text) throws IllegalArgumentException {
        ObjectMapper mapper = new ObjectMapper();
        final FilterMap filterMap = new FilterMap();
        try {
            final List<Filter> filters = mapper.readValue(text, TypeFactory.defaultInstance().constructCollectionType(ArrayList.class, Filter.class));
 
            for (Filter filter : filters) {
                filterMap.addFilter(filter);
            }
        } catch (IOException ex) {
            LOGGER.error(ex.getMessage(), ex);
        }
 
        setValue(filterMap);
    }
 
}
SortingMapEditor.java
package be.mentoringsystems.merke.presentation.conversion;
 
import be.mentoringsystems.merke.model.Sorting;
import be.mentoringsystems.merke.model.SortingMap;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import java.beans.PropertyEditorSupport;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
 
/**
 *
 * @author anthonyarents
 */
public class SortingMapEditor extends PropertyEditorSupport {
 
    private static final Logger LOGGER = LogManager.getLogger(SortingMapEditor.class);
 
    @Override
    public void setAsText(final String text) throws IllegalArgumentException {
        ObjectMapper mapper = new ObjectMapper();
        final SortingMap sortingMap = new SortingMap();
        try {
            final List<Sorting> sorters = mapper.readValue(text, TypeFactory.defaultInstance().constructCollectionType(ArrayList.class, Sorting.class));
            sortingMap.setSortings(sorters);
        } catch (IOException ex) {
            LOGGER.error(ex.getMessage(), ex);
        }
 
        setValue(sortingMap);
    }
 
}

Security Annotations

Several guides are available for this, I recommend to just burn them all.

Annotations provided by Spring Security

Method Security (aka do it yourself)

@methodSecurity.hasLoginAccess(#id)
MethodSecurity.java
package be.mentoringsystems.merke.security;
 
import be.mentoringsystems.merke.model.QueryParams;
import be.mentoringsystems.merke.model.db.Attendance;
import be.mentoringsystems.merke.model.db.Certificate;
import be.mentoringsystems.merke.model.db.Context;
import be.mentoringsystems.merke.model.db.File;
import be.mentoringsystems.merke.model.db.Login;
import be.mentoringsystems.merke.model.db.Remark;
import be.mentoringsystems.merke.model.db.Vendor;
import be.mentoringsystems.merke.model.db.VendorRequest;
import be.mentoringsystems.merke.model.db.Vendorstall;
import be.mentoringsystems.merke.model.db.VendorstallPlacement;
import be.mentoringsystems.merke.model.dto.AttendanceDTO;
import be.mentoringsystems.merke.model.dto.CertificateDTO;
import be.mentoringsystems.merke.model.dto.FileDTO;
import be.mentoringsystems.merke.model.dto.LoginDTO;
import be.mentoringsystems.merke.model.dto.RemarkDTO;
import be.mentoringsystems.merke.model.dto.VendorDTO;
import be.mentoringsystems.merke.model.dto.VendorRequestDTO;
import be.mentoringsystems.merke.model.dto.VendorstallDTO;
import be.mentoringsystems.merke.model.dto.VendorstallPlacementDTO;
import be.mentoringsystems.merke.service.AttendanceService;
import be.mentoringsystems.merke.service.CertificateService;
import be.mentoringsystems.merke.service.ContextService;
import be.mentoringsystems.merke.service.FileService;
import be.mentoringsystems.merke.service.LoginService;
import be.mentoringsystems.merke.service.RemarkService;
import be.mentoringsystems.merke.service.VendorRequestService;
import be.mentoringsystems.merke.service.VendorService;
import be.mentoringsystems.merke.service.VendorstallPlacementService;
import be.mentoringsystems.merke.service.VendorstallService;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Component;
 
/**
 *
 * @author anthonyarents
 */
@Component("methodSecurity")
public class MethodSecurity {
 
    @Autowired
    private transient LoginService loginService;
    @Autowired
    private transient VendorService vendorService;
    @Autowired
    private transient VendorstallService vendorstallService;
    @Autowired
    private transient VendorstallPlacementService vendorstallPlacementService;
    @Autowired
    private transient AttendanceService attendanceService;
    @Autowired
    private transient CertificateService certificateService;
    @Autowired
    private transient FileService fileService;
    @Autowired
    private transient RemarkService remarkService;
    @Autowired
    private transient VendorRequestService vendorRequestService;
    @Autowired
    private transient ContextService contextService;
 
    // QueryParams
    public boolean vendorLimited(final QueryParams queryParams) throws AccessDeniedException {
        final Login login = loginService.getCurrentLogin();
 
        if (!login.isAdmin() && !login.isMSAdmin() && !login.isFireman()) {
            final Vendor vendor = vendorService.getByLoginId(login.getId());
            if (!vendor.getId().toString().equals(queryParams.getFilterValue("vendorId"))) {
                return false;
            }
        }
 
        return true;
    }
 
    // Attendance
    public boolean hasAttendanceAccess(final AttendanceDTO dto, final UUID id) {
        dto.setId(id);
        return hasAttendanceAccess(dto);
    }
 
    public boolean hasAttendanceAccess(final AttendanceDTO dto) {
        boolean hasAccess = true;
        if (dto.getVendorId() != null) {
            hasAccess = hasVendorAccess(dto.getVendorId());
        }
        if (hasAccess && dto.getVendorstallId() != null) {
            hasAccess = hasVendorstallAccess(dto.getVendorstallId());
        }
        if (hasAccess && dto.getId() != null) {
            return hasAttendanceAccess(dto.getId());
        }
        return hasAccess;
    }
 
    public boolean hasAttendanceAccess(final UUID id) {
        boolean hasAccess = true;
        Attendance obj = attendanceService.getById(id);
        if (obj == null) {
            hasAccess = false;
        } else {
            if (hasAccess && obj.getVendorId() != null) {
                hasAccess = hasVendorAccess(obj.getVendorId());
            }
            if (hasAccess && obj.getVendorstallId() != null) {
                hasAccess = hasVendorstallAccess(obj.getVendorstallId());
            }
        }
        return hasAccess;
    }
 
    // Certificate
    public boolean hasCertificateAccess(final CertificateDTO dto, final UUID id) {
        dto.setId(id);
        return hasCertificateAccess(dto);
    }
 
    public boolean hasCertificateAccess(final CertificateDTO dto) {
        boolean hasAccess = true;
        if (dto.getVendorId() != null) {
            hasAccess = hasVendorAccess(dto.getVendorId());
        }
        if (hasAccess && dto.getVendorstallId() != null) {
            hasAccess = hasVendorstallAccess(dto.getVendorstallId());
        }
        if (hasAccess && dto.getId() != null) {
            return hasCertificateAccess(dto.getId());
        }
        return hasAccess;
    }
 
    public boolean hasCertificateAccess(final UUID id) {
        boolean hasAccess = true;
        Certificate obj = certificateService.getById(id);
        if (obj == null) {
            hasAccess = false;
        } else {
            if (hasAccess && obj.getVendorId() != null) {
                hasAccess = hasVendorAccess(obj.getVendorId());
            }
            if (hasAccess && obj.getVendorstallId() != null) {
                hasAccess = hasVendorstallAccess(obj.getVendorstallId());
            }
        }
        return hasAccess;
    }
 
    // Certificate
    public boolean hasFileAccess(final FileDTO dto, final UUID id) {
        dto.setId(id);
        return hasFileAccess(dto);
    }
 
    public boolean hasFileAccess(final FileDTO dto) {
        boolean hasAccess = true;
        if (dto.getVendorId() != null) {
            hasAccess = hasVendorAccess(dto.getVendorId());
        }
        if (hasAccess && dto.getVendorstallId() != null) {
            hasAccess = hasVendorstallAccess(dto.getVendorstallId());
        }
        if (hasAccess && dto.getId() != null) {
            return hasFileAccess(dto.getId());
        }
        return hasAccess;
    }
 
    public boolean hasFileAccess(final UUID id) {
        boolean hasAccess = true;
        File obj = fileService.getById(id);
        if (obj == null) {
            hasAccess = false;
        } else {
            if (hasAccess && obj.getVendorId() != null) {
                hasAccess = hasVendorAccess(obj.getVendorId());
            }
            if (hasAccess && obj.getVendorstallId() != null) {
                hasAccess = hasVendorstallAccess(obj.getVendorstallId());
            }
        }
        return hasAccess;
    }
 
    // Remark
    public boolean hasRemarkAccess(final RemarkDTO dto, final UUID id) {
        dto.setId(id);
        return hasRemarkAccess(dto);
    }
 
    public boolean hasRemarkAccess(final RemarkDTO dto) {
        boolean hasAccess = true;
        if (dto.getVendorId() != null) {
            hasAccess = hasVendorAccess(dto.getVendorId());
        }
        if (hasAccess && dto.getVendorstallId() != null) {
            hasAccess = hasVendorstallAccess(dto.getVendorstallId());
        }
        if (hasAccess && dto.getId() != null) {
            return hasRemarkAccess(dto.getId());
        }
        return hasAccess;
    }
 
    public boolean hasRemarkAccess(final UUID id) {
        boolean hasAccess = true;
        Remark obj = remarkService.getById(id);
        if (obj == null) {
            hasAccess = false;
        } else {
            if (hasAccess && obj.getVendorId() != null) {
                hasAccess = hasVendorAccess(obj.getVendorId());
            }
            if (hasAccess && obj.getVendorstallId() != null) {
                hasAccess = hasVendorstallAccess(obj.getVendorstallId());
            }
        }
        return hasAccess;
    }
 
    // Vendor
    public boolean hasVendorAccess(final VendorDTO dto, final UUID vendorId) {
        dto.setId(vendorId);
        return hasVendorAccess(dto);
    }
 
    public boolean hasVendorAccess(final VendorDTO dto) {
        return hasVendorAccess(dto.getId());
    }
 
    public boolean hasVendorAccess(final UUID vendorId) {
        Login login = loginService.getCurrentLogin();
        return hasVendorAccess(login, vendorId);
    }
 
    // VendorRequest
    public boolean hasVendorRequestAccess(final VendorRequestDTO dto, final UUID id) {
        dto.setId(id);
        return hasVendorRequestAccess(dto);
    }
 
    public boolean hasVendorRequestAccess(final VendorRequestDTO dto) {
        boolean hasAccess = true;
        if (dto.getVendorId() != null) {
            hasAccess = hasVendorAccess(dto.getVendorId());
        }
        if (hasAccess && dto.getVendorstallId() != null) {
            hasAccess = hasVendorstallAccess(dto.getVendorstallId());
        }
        if (hasAccess && dto.getId() != null) {
            return hasVendorRequestAccess(dto.getId());
        }
        return hasAccess;
    }
 
    public boolean hasVendorRequestAccess(final UUID id) {
        boolean hasAccess = true;
        VendorRequest obj = vendorRequestService.getById(id);
        if (obj == null) {
            hasAccess = false;
        } else {
            if (hasAccess && obj.getVendorId() != null) {
                hasAccess = hasVendorAccess(obj.getVendorId());
            }
            if (hasAccess && obj.getVendorstallId() != null) {
                hasAccess = hasVendorstallAccess(obj.getVendorstallId());
            }
        }
        return hasAccess;
    }
 
    // Vendorstall
    public boolean hasVendorstallAccess(final VendorstallDTO dto, final UUID vendorstallId) {
        dto.setId(vendorstallId);
        return hasVendorstallAccess(dto);
    }
 
    public boolean hasVendorstallAccess(final VendorstallDTO dto) {
        return hasVendorstallAccess(dto.getId());
    }
 
    public boolean hasVendorstallAccess(final UUID vendorstallId) {
        Login login = loginService.getCurrentLogin();
        return hasVendorstallAccess(login, vendorstallId);
    }
 
    public boolean hasVendorstallPlacementAccess(final VendorstallPlacementDTO dto, final UUID vendorstallPlacementId) {
        dto.setId(vendorstallPlacementId);
        return hasVendorstallPlacementAccess(dto);
    }
 
    public boolean hasVendorstallPlacementAccess(final VendorstallPlacementDTO dto) {
        return hasVendorstallPlacementAccess(dto.getId());
    }
 
    public boolean hasVendorstallPlacementAccess(final UUID vendorstallPlacementId) {
        Login login = loginService.getCurrentLogin();
        return hasVendorstallPlacementAccess(login, vendorstallPlacementId);
    }
 
    public boolean hasLoginAccess(final LoginDTO dto, final UUID loginId) {
        dto.setId(loginId);
        return hasLoginAccess(dto);
    }
 
    public boolean hasLoginAccess(final LoginDTO dto) {
        return hasLoginAccess(dto.getId());
    }
 
    public boolean hasLoginAccess(final UUID loginId) {
        Login login = loginService.getCurrentLogin();
        return hasLoginAccess(login, loginId);
    }
 
    public boolean hasLoginAccess(final Login login, final UUID loginId) {
        if (login == null) {
            return false;
        } else if (login.isMSAdmin()) {
            return true;
        } else {
            if (loginId == null) {
                return false;
            } else {
                if (loginId.equals(login.getId())) {
                    return true;
                } else {
                    if (login.getContextId() == null) {
                        return false;
                    } else {
                        final Context context = contextService.getById(login.getContextId());
                        final Vendor v = vendorService.getByLoginId(loginId);
                        if (v != null && v.getContexts().contains(context)) {
                            return login.isAdmin() || login.isFireman() || login.isPolice();
                        } else {
                            return false;
                        }
                    }
                }
            }
        }
    }
 
    // HelperMethods
    private boolean hasVendorAccess(final Login login, final UUID vendorId) {
        if (login == null) {
            return false;
        } else if (login.isMSAdmin()) {
            return true;
        } else {
            if (vendorId == null) {
                return false;
            } else {
                Vendor v = vendorService.getById(vendorId);
                if (v == null) {
                    return false;
                } else {
                    if (v.getLoginId().equals(login.getId())) {
                        return true;
                    } else {
                        if (login.getContextId() == null) {
                            return false;
                        } else {
                            final Context context = contextService.getById(login.getContextId());
                            if (v.getContexts().contains(context)) {
                                return login.isAdmin() || login.isFireman() || login.isPolice();
                            } else {
                                return false;
                            }
                        }
                    }
                }
            }
        }
    }
 
    private boolean hasVendorAdminAccess(final Login login, final Vendor vendor) {
        if (vendor == null) {
            return login.isMSAdmin();
        } else {
            //TODO extra checks for vendor
            return true;
        }
    }
 
    public boolean hasVendorAdminAccess(final UUID vendorId) {
        final Login login = loginService.getCurrentLogin();
        return hasVendorAdminAccess(login, vendorId);
    }
 
    private boolean hasVendorAdminAccess(final Login login, final UUID vendorId) {
        if (login == null) {
            return false;
        } else if (login.isMSAdmin()) {
            return true;
        } else if (login.isAdmin()) {
            if (vendorId == null) {
                return true; // creating a new obj
            } else {
                final Vendor v = vendorService.getById(vendorId);
                return hasVendorAdminAccess(login, v);
            }
        } else {
            return false;
        }
    }
 
    private boolean hasVendorstallAccess(final Login login, final UUID vendorstallId) {
        if (login == null) {
            return false;
        } else if (login.isMSAdmin()) {
            return true;
        } else {
            if (vendorstallId == null) {
                return true;
            } else {
                final Vendorstall vendorstall = vendorstallService.getById(vendorstallId);
                if (vendorstall == null) {
                    return false;
                } else {
                    return hasVendorAccess(login, vendorstall.getVendorId());
                }
            }
        }
    }
 
    private boolean hasVendorstallPlacementAccess(final Login login, final UUID vendorstallPlacementId) {
        if (login == null) {
            return false;
        } else if (login.isMSAdmin()) {
            return true;
        } else {
            if (vendorstallPlacementId == null) {
                return true;
            } else {
                final VendorstallPlacement vendorstallPlacement = vendorstallPlacementService.getById(vendorstallPlacementId);
                if (vendorstallPlacement == null) {
                    return false;
                } else {
                    return hasVendorstallAccess(login, vendorstallPlacement.getVendorstallId());
                }
            }
        }
    }
 
}

(Optional) CSRF - Cross site Request Forgery

Usually, CSRF is turned off (it's a lot of extra stuff to do), CSRF protection requires you to secure POST, PUT, DELETE (not GET)

Configuration change

Comment out a line in our SecurityConfiguration :

    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        //http.csrf().disable();
        ...
    }

Controller

CsrfController.java
package be.mentoringsystems.abstracts.presentation;
 
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
 
@Controller
public class CsrfController {
 
	@RequestMapping("/csrf")
    @ResponseBody
	public CsrfToken csrf(CsrfToken token) {
		return token;
	}
}

Example javascript usage (jquery)

// Need to use a get request to get the active csrf token (renews token before usage)
function useToken(callback) {
    $.ajax('csrf', {
        method: 'get'
    }).done(function (json) {
        callback.call(this, json);
    });
}
 
// csrf param example
useToken(function(csrf) {
    var data = {
        agree: $('#privacyagree').is(":checked")
    };
    data[csrf.parameterName] = csrf.token;
    $.ajax('privacyCheck', {
        method: 'post',
        data: data
    }).done(function (json) {
        if(json.success) {
            $('#privacyModal').modal('hide');
        } else {
            $('#message').html("It's not possible to continue without accepting the privacy policy.");
            $('#myModal').modal();
        }
    });
});
 
// csrf header example
useToken(function(csrf) {
    var data = {
        agree: $('#privacyagree').is(":checked")
    };
    var headers = {};
    headers['Content-Type'] = 'application/json';
    headers[csrf.headerName] = csrf.token;
    $.ajax('privacyCheck', {
        method: 'post',
        data: data,
        headers: headers,
        dataType: 'json'
    }).done(function (json) {
        if(json.success) {
            $('#privacyModal').modal('hide');
        } else {
            $('#message').html("It's not possible to continue without accepting the privacy policy.");
            $('#myModal').modal();
        }
    });
});

Logout

As mentionned earlier, Logout needs to be POST + secured by the csrf token

the following javascript is used

$('#logoutbtn').click(function () {
    useToken(function(csrf){
        var el = '<input type="hidden" name="' + csrf.parameterName + '" value="' + csrf.token + '"></input>';
        $('#logout').append(el);
        $('#logout').submit();
    });
});


This also requires a hidden form :

<form id="logout" action="logout" method="post" style="display:none;"></form>

JSON web tokens

For adding JWT support

Add to pom:

<auth0.jwt>3.4.1</auth0.jwt>
 
<dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>${auth0.jwt}</version>
</dependency>

Helper classes

JWSHelper.java
package be.mentoringsystems.merke.security;
 
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.nio.charset.StandardCharsets;
import javax.servlet.http.HttpServletRequest;
 
/**
 *
 * @author anthonyarents
 */
public final class JWSHelper {
 
    private static final String SECRET = "F6deJ;xShXD+juV,%dtt}3/z:pZ6-H";
    public static final String TOKEN_PREFIX = "Bearer ";
    public static final String HEADER_NAME = "Authorization";
 
    private JWSHelper() {
        //
    }
 
    public static String getJwsSubject(final HttpServletRequest hsr) {
        String subject = null;
        String authorization = hsr.getHeader(HEADER_NAME);
        if (authorization == null) {
            authorization = hsr.getParameter(HEADER_NAME);
        }
        if (authorization != null && authorization.startsWith(TOKEN_PREFIX)) {
            DecodedJWT decoded = JWT.require(Algorithm.HMAC512(SECRET.getBytes(StandardCharsets.UTF_8))).build().verify(authorization.replace(TOKEN_PREFIX, ""));
            subject = decoded.getSubject();
        }
        return subject;
    }
 
    public static String generateJwsWithSubject(final String subject) {
        return JWT.create().withSubject(subject).sign(Algorithm.HMAC512(SECRET.getBytes(StandardCharsets.UTF_8)));
    }
}
JWTAuthenticationFilter.java
package be.mentoringsystems.merke.security;
 
import be.mentoringsystems.merke.model.db.Login;
import be.mentoringsystems.merke.service.LoginService;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
 
/**
 *
 * @author anthonyarents
 */
@Component
public class JWTAuthenticationFilter implements Filter {
 
    @Autowired
    private LoginService loginService;
    private static final Logger LOGGER = LogManager.getLogger(JWTAuthenticationFilter.class);
 
    @Override
    public void doFilter(final ServletRequest sr, final ServletResponse sr1, final FilterChain fc) throws IOException, ServletException {
        if (sr instanceof HttpServletRequest) {
            final HttpServletRequest hsr = (HttpServletRequest) sr;
            final HttpServletResponse hsr1 = (HttpServletResponse) sr1;
            String username = JWSHelper.getJwsSubject(hsr);
 
            Login login = loginService.getCurrentLogin();
            if (username == null) {
                LOGGER.debug("found no jwt token");
                if (login != null) {
                    LOGGER.debug("user is logged in, making a new jwt token");
                    String token = JWSHelper.generateJwsWithSubject(login.getEmail());
                    hsr1.addHeader(JWSHelper.HEADER_NAME, JWSHelper.TOKEN_PREFIX + token);
                }
            } else {
                LOGGER.debug("found jwt token");
                if (login == null) {
                    UserDetails details = loginService.getByUsername(username);
                    if (details != null) {
                        details = loginService.loadUserByUsername(username);
                        LOGGER.debug("user is not logged in, logging in as {0}", details.getUsername());
                        UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(details, null, details.getAuthorities());
                        SecurityContextHolder.getContext().setAuthentication(auth);
                    }
                }
                //hsr1.addHeader(JWSHelper.HEADER_NAME, JWSHelper.TOKEN_PREFIX + token);
            }
        }
        fc.doFilter(sr, sr1);
    }
 
    @Override
    public void init(final FilterConfig fc) throws ServletException {
    }
 
    @Override
    public void destroy() {
    }
 
}

Add method to loginController:

    @RequestMapping(value = "/authenticate", method = RequestMethod.POST)
    public ApiDTO authenticate(@RequestParam(required = false) final String username, @RequestParam(required = false) final String 
    password, final HttpServletRequest hsr) {
        final ApiDTO dto = ApiDTO.error();
 
        String session = JWSHelper.getJwsSubject(hsr);
        Login login = null;
        if (session != null) {
            login = loginService.getByUsername(session);
        }
        if (login == null) {
            login = loginService.getByUsernameAndPassword(username, password);
        }
 
        if (login != null) {
            String jws = JWSHelper.generateJwsWithSubject(login.getUsername());
            login.setJwt(jws);
            dto.setData(login);
            dto.setSuccess(Boolean.TRUE);
        }
 
        return dto;
    }
 

Add transient jwt parameter to Login:

    private String jwt;
 
    @Transient
    public String getJwt() {
        return jwt;
    }
    public void setJwt(final String jwt) {
        this.jwt = jwt;
    }  

Add method to LoginServiceImpl if it doesn't exist:

    @Override
    @Transactional(readOnly = true)
    public Login getByUsernameAndPassword(final String username, final String password) {
        final Login login = getByUsername(username);
        if (login == null) {
            return null;
        } else {
            if (passwordEncoder.matches(password, login.getPassword())) {
                return login;
            } else {
                return null;
            }
        }
    }

Add filter to securityConfiguration

package be.trustandchain.mobi.config;
 
import be.trustandchain.mobi.security.JWTAuthenticationFilter;
import be.trustandchain.mobi.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
 
/**
 *
 * @author anthonyarents
 */
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
 
    @Autowired
    private LoginService loginService;
    @Autowired
    private JWTAuthenticationFilter jwtAuthenticationFilter;
 
    @Override
    public void configure(final WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/static/**", "/app/**");
    }
 
    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        // temporary
        http.csrf().disable();
        http.headers().frameOptions().sameOrigin();
        http.userDetailsService(loginService);
 
        http
                .logout().logoutUrl("/logout").permitAll() // logout page, permitall is necessary
                .and()
                .authorizeRequests() // request matching with ant
                .antMatchers("/rest/**").permitAll()
                .anyRequest().permitAll(); // we're not checking if ppl are logged in, we do rolechecks on individual methods
 
        http.addFilterAfter(jwtAuthenticationFilter, BasicAuthenticationFilter.class);
    }
 
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
 
    @Override
    protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
        PasswordEncoder passwordEncoder = passwordEncoder();
        auth.userDetailsService(loginService).passwordEncoder(passwordEncoder);
        /*LOGGER.info("admin");
        LOGGER.info(passwordEncoder.encode("admin"));
        LOGGER.info("p55VrKYE");
        LOGGER.info(passwordEncoder.encode("p55VrKYE"));
        LOGGER.info("dD6fd7bT");
        LOGGER.info(passwordEncoder.encode("dD6fd7bT"));
        LOGGER.info("s7cvJJC3");
        LOGGER.info(passwordEncoder.encode("s7cvJJC3"));
        LOGGER.info("GwcEA9hP");
        LOGGER.info(passwordEncoder.encode("GwcEA9hP"));
        LOGGER.info("RFYuV9ac");
        LOGGER.info(passwordEncoder.encode("RFYuV9ac"));
        LOGGER.info("ZBjMnY5E");
        LOGGER.info(passwordEncoder.encode("ZBjMnY5E"));
        LOGGER.info("reTkBWJFtUQnEV3R");
        LOGGER.info(passwordEncoder.encode("reTkBWJFtUQnEV3R"));*/
    }
 
}

Server-side validation

At this time, I've only added a check to disallow “dangerous” html on fields which will display the html as is (instead of htmlEncoded like the rest of the output)

extra requirements on the project :

  • el-api (v2.2 is used for tomcat6 & tomcat7, v3.0.0 is used with java8 & tomcat8)
  • hibernate-validator (v5.4.1.Final in case of tomcat6 & 7, use latest in case of java8 & tomcat8)
  • jsoup (use latest)

DTO

imports

import org.hibernate.validator.constraints.SafeHtml;

Getter

@SafeHtml
    public String getDescription() {
        return description;
    }

Presentation

Imports

import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;

Changes to methods updateById & createNew

@JsonView(View.Public.class)
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
    @ResponseStatus(HttpStatus.OK)
    public ApiDTO updateById(@PathVariable final UUID id, @RequestBody @Validated final NotificationDTO dto, final BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return ApiDTO.error();
        }
        dto.setId(id);
        final Notification result = notificationService.save(dto);
        return new ApiDTO(result);
    }
 
    @JsonView(View.Public.class)
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @RequestMapping(value = "", method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.CREATED)
    public ApiDTO createNew(@RequestBody @Validated final NotificationDTO dto, final BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return ApiDTO.error();
        }
        final Notification result = notificationService.save(dto);
        return new ApiDTO(result);
    }

Locale handling

Additional MvcConfiguration

@Bean
    public LocaleResolver localeResolver(){
        CustomLocaleResolver resolver = new CustomLocaleResolver();
        resolver.setDefaultLocale(new Locale("nl"));
        resolver.setCookieName("myLocaleCookie");
        resolver.setCookieMaxAge(4800);
        return resolver;
    }
 
    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor(){
        LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
        interceptor.setParamName("mylocale");
        return interceptor;
    }
 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
    }

Locale resolver

CustomLocaleResolver.java
package be.mentoringsystems.merke.presentation.locale;
 
import be.mentoringsystems.merke.model.db.Login;
import be.mentoringsystems.merke.service.LoginService;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import static org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE_REQUEST_ATTRIBUTE_NAME;
import static org.springframework.web.servlet.i18n.CookieLocaleResolver.TIME_ZONE_REQUEST_ATTRIBUTE_NAME;
 
/**
 *
 * @author anthonyarents
 */
public class CustomLocaleResolver extends CookieLocaleResolver {
 
    @Autowired
    private LoginService loginService;
 
    @Override
    public Locale resolveLocale(final HttpServletRequest request) {
        final Login login = loginService.getCurrentLogin();
        if (login != null) {
            final Locale locale = new Locale(login.getLanguage());
            request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME, locale);
            request.setAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME, determineDefaultTimeZone(request));
            return locale;
        } else {
            return super.resolveLocale(request);
        }
    }
}

Pdf handling

For filling Pdf fields : There's a PdfFieldInspector (project is in our projects SVN)

java -jar PdfFieldInspector-1.0-SNAPSHOT-jar-with-dependencies.jar /Users/anthonyarents/NetBeansProjects/JCPIS_ANALYSE_BUILD_SUITE/src/main/gevas/WEB-INF/report/pdf/gevas/Maklervollmacht\ AcontrA.pdf

Excel handling

ExportController

ExportController.java
package be.mentoringsystems.merke.presentation;
 
import be.mentoringsystems.merke.model.QueryParams;
import be.mentoringsystems.merke.presentation.excel.VendorRequests;
import be.mentoringsystems.merke.presentation.excel.Vendors;
import be.mentoringsystems.merke.service.VendorRequestService;
import be.mentoringsystems.merke.service.VendorService;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
 
/**
 *
 * @author anthonyarents
 */
@RestController
@RequestMapping("/export")
public class ExportController {
 
    @Autowired
    private transient VendorRequestService vendorRequestService;
    @Autowired
    private transient VendorService vendorService;
 
    @RequestMapping(value = "/vendorRequests", method = RequestMethod.POST)
    public ModelAndView exportVendorRequests(QueryParams queryParams, 
            @RequestParam(name = "waitlist", defaultValue = "false", required = false) Boolean waitlist) {
        // remove paging
        queryParams.setLimit(0);
        queryParams.setStart(0);
 
        final Map<String, Object> objectMap = new HashMap<String, Object>();
        objectMap.put("data", vendorRequestService.getAll(queryParams));
        objectMap.put("waitlist", waitlist);
 
        final VendorRequests view = new VendorRequests();
        return new ModelAndView(view, objectMap);
    }
 
    @RequestMapping(value = "/vendors", method = RequestMethod.POST)
    public ModelAndView exportVendors(QueryParams queryParams) {
        // remove paging
        queryParams.setLimit(0);
        queryParams.setStart(0);
 
        final Map<String, Object> objectMap = new HashMap<String, Object>();
        objectMap.put("data", vendorService.getAll(queryParams));
 
        final Vendors view = new Vendors();
        return new ModelAndView(view, objectMap);
    }
}

ExcelView

VendorRequests.java
package be.mentoringsystems.merke.presentation.excel;
 
import be.mentoringsystems.merke.model.db.VendorRequest;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.springframework.web.servlet.view.document.AbstractXlsxView;
 
/**
 *
 * @author anthonyarents
 */
public class VendorRequests extends AbstractXlsxView{
 
    private void styledCell(Cell cell, String value, CellStyle style) {
        cell.setCellValue(value);
        cell.setCellStyle(style);
    }
 
    @Override
    protected void buildExcelDocument(Map<String, Object> map, Workbook wrkbk, HttpServletRequest hsr, HttpServletResponse hsr1) throws Exception {
        @SuppressWarnings("unchecked")
        final List<VendorRequest> data = (List<VendorRequest>) map.get("data");
        final Boolean waitlist = (Boolean) map.get("waitlist");
        String documentName = "Aanvragen overzicht";
        String documentTitle = "Export overzicht ";
        if(Boolean.TRUE.equals(waitlist)) {
            documentName = "Aanvragen wachtlijst";
            documentTitle = "Export wachtlijst ";
        }
 
        final SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm");
        hsr1.setHeader("Content-Disposition", "attachment; filename=\"" + documentName + ".xlsx\"");
 
        final CellStyle acceptedStyle = wrkbk.createCellStyle();
        acceptedStyle.setFillForegroundColor(IndexedColors.LIGHT_GREEN.getIndex());
        acceptedStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        final CellStyle declinedStyle = wrkbk.createCellStyle();
        declinedStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        declinedStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        final CellStyle headerStyle = wrkbk.createCellStyle();
        headerStyle.setFillForegroundColor(IndexedColors.PALE_BLUE  .getIndex());
        headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
 
        final Sheet sheet = wrkbk.createSheet();
        final Row titlerow = sheet.createRow(0);
 
        sheet.addMergedRegion(new CellRangeAddress(0,0,0,6));
        titlerow.createCell(0).setCellValue(documentTitle + sdf.format(new Date()));
 
        final Row headerrow = sheet.createRow(1);
        styledCell(headerrow.createCell(0), "Wachtnr.", headerStyle);
        styledCell(headerrow.createCell(1), "Voornaam", headerStyle);
        styledCell(headerrow.createCell(2), "Naam", headerStyle);
        styledCell(headerrow.createCell(3), "E-mailadres", headerStyle);
        styledCell(headerrow.createCell(4), "Tel", headerStyle);
        styledCell(headerrow.createCell(5), "Datum", headerStyle);
        styledCell(headerrow.createCell(6), "Status", headerStyle);
 
        int rowcounter = 0;
        int rowoffset = 2;
        for(VendorRequest request : data) {
            final Row row = sheet.createRow(rowcounter + rowoffset);
            rowcounter++;
 
            row.createCell(0).setCellValue(rowcounter);
            row.createCell(1).setCellValue(request.getVendor().getFirstname());
            row.createCell(2).setCellValue(request.getVendor().getLastname());
            row.createCell(3).setCellValue(request.getVendor().getEmail());
            row.createCell(4).setCellValue(request.getVendor().getTelephonenumber());
            row.createCell(5).setCellValue(sdf.format(request.getCreatedOn()));
            if(Boolean.TRUE.equals(waitlist)) {
                if(VendorRequest.WaitStatus.ACCEPTED.equals(request.getWaitstatus())) {
                    styledCell(row.createCell(6), "Goedgekeurd", acceptedStyle);
                } else {
                    row.createCell(6).setCellValue("In afwachting");
                }
            } else {
                switch (request.getStatus()) {
                    case ACCEPTED:
                        styledCell(row.createCell(6), "Goedgekeurd", acceptedStyle);
                        break;
                    case DECLINED:
                        styledCell(row.createCell(6), "Afgekeurd", declinedStyle);
                        break;
                    default:
                        row.createCell(6).setCellValue("In afwachting");
                        break;
                }
            }
        }
 
        sheet.autoSizeColumn(0, false);
        sheet.autoSizeColumn(1, false);
        sheet.autoSizeColumn(2, false);
        sheet.autoSizeColumn(3, false);
        sheet.autoSizeColumn(4, false);
        sheet.autoSizeColumn(5, false);
        sheet.autoSizeColumn(6, false);
    }
}
Vendors.java
package be.mentoringsystems.merke.presentation.excel;
 
import be.mentoringsystems.merke.model.db.Category;
import be.mentoringsystems.merke.model.db.Vendor;
import be.mentoringsystems.merke.model.db.Vendorstall;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.springframework.web.servlet.view.document.AbstractXlsxView;
 
/**
 *
 * @author anthonyarents
 */
public class Vendors extends AbstractXlsxView{
 
    private void styledCell(Cell cell, String value, CellStyle style) {
        cell.setCellValue(value);
        cell.setCellStyle(style);
    }
 
    @Override
    protected void buildExcelDocument(Map<String, Object> map, Workbook wrkbk, HttpServletRequest hsr, HttpServletResponse hsr1) throws Exception {
        @SuppressWarnings("unchecked")
        final List<Vendor> data = (List<Vendor>) map.get("data");
        String documentName = "Overzicht standhouders";
        String documentTitle = "Export overzicht standhouders ";
 
        final SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm");
        hsr1.setHeader("Content-Disposition", "attachment; filename=\"" + documentName + ".xlsx\"");
 
        final CellStyle headerStyle = wrkbk.createCellStyle();
        headerStyle.setFillForegroundColor(IndexedColors.PALE_BLUE  .getIndex());
        headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
 
        final Sheet sheet = wrkbk.createSheet();
        final Row titlerow = sheet.createRow(0);
 
        sheet.addMergedRegion(new CellRangeAddress(0,0,0,8));
        titlerow.createCell(0).setCellValue(documentTitle + sdf.format(new Date()));
 
        final Row headerrow = sheet.createRow(1);
        styledCell(headerrow.createCell(0), "Nr.", headerStyle);
        styledCell(headerrow.createCell(1), "Voornaam", headerStyle);
        styledCell(headerrow.createCell(2), "Naam", headerStyle);
        styledCell(headerrow.createCell(3), "Bedrijf", headerStyle);
        styledCell(headerrow.createCell(4), "Tel", headerStyle);
        styledCell(headerrow.createCell(5), "E-mailadres", headerStyle);
        styledCell(headerrow.createCell(6), "Product", headerStyle);
        styledCell(headerrow.createCell(7), "Ingeschreven sinds", headerStyle);
        styledCell(headerrow.createCell(8), "Opmerking", headerStyle);
 
        int rowcounter = 0;
        int rowoffset = 2;
        for(Vendor vendor : data) {
            final Row row = sheet.createRow(rowcounter + rowoffset);
            rowcounter++;
 
            row.createCell(0).setCellValue(rowcounter);
            row.createCell(1).setCellValue(vendor.getFirstname());
            row.createCell(2).setCellValue(vendor.getLastname());
            row.createCell(3).setCellValue(vendor.getCompanyName());
            row.createCell(4).setCellValue(vendor.getTelephonenumber());
            row.createCell(5).setCellValue(vendor.getEmail());
 
            List<Category> categories = new ArrayList<Category>();
            for(Vendorstall stall : vendor.getVendorstalls()) {
                for(Category cat : stall.getCategories()) {
                    if(!categories.contains(cat)) {
                        categories.add(cat);
                    }
                }
            }
            StringBuilder categoryString = new StringBuilder();
            for(int i = 0; i < categories.size(); i++) {
                if(i > 0) {
                    categoryString.append(", ");
                }
                categoryString.append(categories.get(i).getName());
            }
            row.createCell(6).setCellValue(categoryString.toString());
 
            row.createCell(7).setCellValue(sdf.format(vendor.getCreatedOn()));
            row.createCell(8).setCellValue("Nee");
 
        }
 
        sheet.autoSizeColumn(0, false);
        sheet.autoSizeColumn(1, false);
        sheet.autoSizeColumn(2, false);
        sheet.autoSizeColumn(3, false);
        sheet.autoSizeColumn(4, false);
        sheet.autoSizeColumn(5, false);
        sheet.autoSizeColumn(6, false);
        sheet.autoSizeColumn(7, false);
        sheet.autoSizeColumn(8, false);
 
    }
 
}

ImportController

ImportController.java
package be.mentoringsystems.merke.presentation;
 
import be.mentoringsystems.merke.importer.VendorImporter;
import java.util.UUID;
import javax.servlet.ServletContext;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
 
/**
 *
 * @author anthonyarents
 */
@RestController
@RequestMapping("/import")
public class ImportController {
 
    @Value("#{contextParameters.uploadPath}")
    private String uploadPath;
 
    @Autowired
    private ServletContext servletContext;
 
    @Autowired
    private VendorImporter vendorImporter;
 
    private static final Logger LOGGER = LogManager.getLogger(ImportController.class);
 
    @RequestMapping(value = "/vendors", method = RequestMethod.POST, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}, produces = {MediaType.TEXT_PLAIN_VALUE, MediaType.TEXT_HTML_VALUE, MediaType.APPLICATION_JSON_VALUE})
    public String importVendors(
            @RequestPart(name = "file") MultipartFile file,
            @RequestParam(name = "name") String name) throws Exception {
        if (file != null && !file.isEmpty()) {
            LOGGER.info("receiving file.");
            UUID fileId = UUID.randomUUID();
            StringBuilder uploadPathBuilder = new StringBuilder(uploadPath);
            uploadPathBuilder.append("/").append(fileId.toString());
 
            String mimeType = servletContext.getMimeType(file.getOriginalFilename());
            String extension = FilenameUtils.getExtension(file.getOriginalFilename());
 
			if (mimeType == null) {
                // Set to binary type if MIME mapping isnot found.
                mimeType = "application/octet-stream";
            }
 
            java.io.File serverFile = new java.io.File(uploadPathBuilder.toString() + "." + extension);
            byte[] bytes = file.getBytes();
            FileUtils.writeByteArrayToFile(serverFile, bytes);
            LOGGER.info("starting import.");
 
            vendorImporter.importFile(serverFile, fileId);
 
            //fileService.saveFile(fileDTO);
            LOGGER.info("File upload complete.");
 
            return "{\"success\": true }";
        } else {
            throw new Exception("You failed to upload " + name + " because the file was empty.");
        }
    }
}

Importer Interface

VendorImporter.java
package be.mentoringsystems.merke.importer;
 
import java.io.File;
import java.util.UUID;
 
/**
 *
 * @author anthonyarents
 */
public interface VendorImporter {
 
    void importFile(final File file, final UUID fileId);
}

Importer Implementation

in the method private void mapRow(final Row row, final List<String> columnHeaders, final UUID fileId)
values is used to import instead of result. Usually importing would be done with the result map (since it has the column names).
In this case the format of imported files varied a lot.

VendorImporterImpl.java
package be.mentoringsystems.merke.importer.excel;
 
import be.mentoringsystems.merke.importer.VendorImporter;
import be.mentoringsystems.merke.model.db.Login;
import be.mentoringsystems.merke.model.db.Vendor;
import be.mentoringsystems.merke.model.db.Vendorstall;
import be.mentoringsystems.merke.service.LoginService;
import be.mentoringsystems.merke.service.VendorService;
import be.mentoringsystems.merke.service.VendorstallService;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.ss.util.CellReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
/**
 *
 * @author anthonyarents
 */
@Component
public class VendorImporterImpl implements VendorImporter {
 
    @Autowired
    private transient VendorService vendorService;
    @Autowired
    private transient VendorstallService vendorstallService;
    @Autowired
    private transient LoginService loginService;
 
    private static final Logger LOGGER = LogManager.getLogger(VendorImporterImpl.class);
 
    @Override
    public void importFile(File file, final UUID fileId) {
        try {
            //InputStream inp = new FileInputStream("workbook.xlsx");
            final Workbook wb = WorkbookFactory.create(file);
            final Sheet sheet = wb.getSheetAt(0); // only first sheet
 
            final List<String> columnHeaders = new ArrayList<String>();
            for (Row row : sheet) {
                if (columnHeaders.isEmpty()) {
                    readColumnHeaders(row, columnHeaders);
                } else {
                    mapRow(row, columnHeaders, fileId);
                }
            }
 
        } catch (IOException ex) {
            LOGGER.error(ex.getMessage(), ex);
        } catch (InvalidFormatException ex) {
            LOGGER.error(ex.getMessage(), ex);
        } catch (EncryptedDocumentException ex) {
            LOGGER.error(ex.getMessage(), ex);
        }
    }
 
    @SuppressWarnings("deprecation")
    private Object getCellContent(final Cell cell) {
        // Alternatively, get the value and format it yourself
        switch (cell.getCellTypeEnum()) {
            case STRING:
                return cell.getRichStringCellValue().getString();
            case NUMERIC:
                if (DateUtil.isCellDateFormatted(cell)) {
                    return cell.getDateCellValue();
                } else {
                    return cell.getNumericCellValue();
                }
            case BOOLEAN:
                return cell.getBooleanCellValue();
            case FORMULA:
                return cell.getCellFormula();
            case BLANK:
                return "";
            default:
                return null;
        }
    }
 
    private String getAsString(final Object obj) {
        if(obj == null) return null;
        if(obj instanceof String) return (String) obj;
        return obj.toString();
    }
 
    private void mapVendorAndVendorstall(final List<String> columnHeaders, final List<Object> values, final UUID fileId) {
        LOGGER.info("--- mapping to vendor and vendorstall");
        final Vendor vendor = new Vendor();
        final Vendorstall vendorstall = new Vendorstall();
        final Login login = new Login();
 
        vendor.setTitle((String) values.get(0));
        vendor.setTitle2((String) values.get(1));
        vendor.setFirstname((String) values.get(2));
        vendor.setLastname((String) values.get(3));
        vendor.setCompanyName((String) values.get(4));
        vendor.setStreet((String) values.get(5));
        Object zipcodeObj = values.get(6);
        if(zipcodeObj instanceof String) {
            vendor.setPostalcode((String) zipcodeObj);
        } else {
            Double zipcode = (Double) values.get(6);
            vendor.setPostalcode(Integer.toString(zipcode.intValue()));
        }
        vendor.setCity((String) values.get(7));
        vendor.setTelephonenumber((String) values.get(8));
        vendor.setEmail((String) values.get(9));
        vendor.setImportlistId(fileId);
 
        vendorstall.setVendor(vendor);
 
        StringBuilder unabletoimport = new StringBuilder();
 
        unabletoimport.append(columnHeaders.get(10)).append(": ").append(values.get(10)).append(",\n"); // 10 : koopwaar
        unabletoimport.append(columnHeaders.get(11)).append(": ").append(values.get(11)).append(",\n"); // 11 : koopwaar beschrijving
        unabletoimport.append(columnHeaders.get(12)).append(": ").append(values.get(12)).append(",\n"); // 12 : koopwaar beschrijving cbs
        unabletoimport.append(columnHeaders.get(13)).append(": ").append(values.get(13)).append(",\n"); // 13 : locatie (plaats op markt)
        unabletoimport.append(columnHeaders.get(14)).append(": ").append(values.get(14)); // 14 : stand nr
 
        vendorstall.setStalllength(getAsString(values.get(15)));
        vendorstall.setStalldepth(getAsString(values.get(16)));
 
        String vehicle = getAsString(values.get(17));
        if(!StringUtils.isEmpty(vehicle) && !"-".equals(vehicle) && !"x".equals(vehicle)) {
            vendorstall.setVehicle(true);
            unabletoimport.append(",\n").append(columnHeaders.get(17)).append(": ").append(vehicle); // 17 : marktwagen
        }
        String disselordoor = getAsString(values.get(18));
        if(!StringUtils.isEmpty(disselordoor) && !"-".equals(disselordoor) && !"x".equals(disselordoor)) {
            vendorstall.setAdze(disselordoor.contains("dss:"));
            vendorstall.setDoor(disselordoor.contains("dr:"));
            if(vendorstall.isDoor()) {
                vendorstall.setDoorlocation(disselordoor);
            }
            if(vendorstall.isAdze()) {
                vendorstall.setAdzelocation(disselordoor);
            }
        }
 
        String luifel = getAsString(values.get(19));
        if(!StringUtils.isEmpty(luifel) && !"-".equals(luifel) && !"x".equals(luifel)) {
            unabletoimport.append(",\n").append(columnHeaders.get(19)).append(": ").append(luifel); // 19 : luifel
        }
        String parasolortent = getAsString(values.get(20));
        if(!StringUtils.isEmpty(parasolortent) && !"-".equals(parasolortent) && !"x".equals(parasolortent)) {
            unabletoimport.append(",\n").append(columnHeaders.get(20)).append(": ").append(parasolortent); // 20 : parasol / tent
        }
        String wattage = getAsString(values.get(21));
        if(!StringUtils.isEmpty(wattage) && !"-".equals(wattage) && !"x".equals(wattage)) {
            vendorstall.setElectricity(true);
            vendorstall.setElectricitypower(wattage);
        }
        vendor.setCompanyNumber(getAsString(values.get(22)));
        vendor.setPermitNumber(getAsString(values.get(23)));
 
        String favv = getAsString(values.get(24));
        if(!StringUtils.isEmpty(favv) && !"-".equals(favv) && !"x".equals(favv)) {
            unabletoimport.append(",\n").append(columnHeaders.get(24)).append(": ").append(favv); // 20 : parasol / tent
        }
        vendor.setIdnrNationalRegister((String) values.get(25));
        if(StringUtils.isEmpty(vendor.getCompanyName())) {
            vendor.setCompanyName((String) values.get(26));
        }
        vendor.setBankaccount(getAsString(values.get(27)));
        // 28 : bedrag calculatie (formule)
        String kolom1 = getAsString(values.get(29));
        if(!StringUtils.isEmpty(kolom1) && !"-".equals(kolom1) && !"x".equals(kolom1)) {
            unabletoimport.append(",\n").append(columnHeaders.get(29)).append(": ").append(kolom1); // 20 : parasol / tent
        }
        String kolom2 = getAsString(values.get(30));
        if(!StringUtils.isEmpty(kolom2) && !"-".equals(kolom2) && !"x".equals(kolom2)) {
            unabletoimport.append(",\n").append(columnHeaders.get(30)).append(": ").append(kolom2); // 20 : parasol / tent
        }
        String remarks = getAsString(values.get(31));
        if(!StringUtils.isEmpty(remarks) && !"-".equals(remarks) && !"x".equals(remarks)) {
            unabletoimport.append(",\n").append(columnHeaders.get(31)).append(": ").append(remarks); // 20 : parasol / tent
        }
 
        vendorstall.setVendor(vendor);
        vendorstall.setExtra(unabletoimport.toString());
 
        login.setFirstname(vendor.getFirstname());
        login.setLastname(vendor.getLastname());
        login.setUsername(vendor.getEmail());
        if(StringUtils.isEmpty(login.getUsername())) {
            final StringBuilder sb = new StringBuilder(login.getFirstname());
            sb.append(".").append(login.getLastname());
            login.setUsername(sb.toString());
        }
        login.setPassword("temp");
        login.setActive(false);
        login.setEmail(vendor.getEmail());
        login.setGroup(2);
        loginService.save(login);
 
        vendor.setLoginId(login.getId());
        vendorService.save(vendor);
        vendorstallService.save(vendorstall);
 
 
        LOGGER.info("--- done mapping to vendor and vendorstall");
    }
 
    private void mapRow(final Row row, final List<String> columnHeaders, final UUID fileId) {
        Map<String, Object> result = new HashMap<String, Object>();
        final List<Object> values = new ArrayList<Object>();
        LOGGER.info("--- reading row");
        for (int cn = 0; cn < columnHeaders.size(); cn++) {
            Cell c = row.getCell(cn, Row.MissingCellPolicy.RETURN_BLANK_AS_NULL);
            CellReference cr = new CellReference(row.getRowNum(), cn);
            if (c == null) {
                // The spreadsheet is empty in this cell
                values.add(null);
                LOGGER.info("{}({}) does not have a value", columnHeaders.get(cn), cr.formatAsString());
            } else {
                final Object value = getCellContent(c);
                result.put(columnHeaders.get(cn), value);
                values.add(value);
                LOGGER.info("{}({}) : {}", columnHeaders.get(cn), cr.formatAsString(), value);
            }
        }
        LOGGER.info("--- done reading row");
 
        if(values.get(0) == null) {
            LOGGER.info("--- unable to map row");
        } else {
            mapVendorAndVendorstall(columnHeaders, values, fileId);
        }
    }
 
    private void readColumnHeaders(Row row, List<String> columnHeaders) {
        for (Cell cell : row) {
            columnHeaders.add(cell.getStringCellValue());
        }
    }
 
}

Log4J2 config

Add log4j2.json to /WEB-INF/

log4j2.json
{
    "configuration": {
        "status": "info",
        "appenders": {
            "Console": {
                "name": "STDOUT",
                "PatternLayout": {
                    "pattern": "%d %-5p  [%c{1}] %m %n"
                }
            }
        },
        "ThresholdFilter": {
            "level": "info"
        },
        "loggers": {
            "root": {
                "level": "info",
                "additivity": "false",
                "AppenderRef": {
                    "ref": "STDOUT"
                }
            }
        }
    }
 
}

JSP Pages

Login

Login.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
  <head>
    <title>BoardPortal</title>
 
    <style type="text/css">
        body {
            margin: 0px;
            padding: 0px;
            background-color: #FFFFFF;
            font-family: "Open Sans","Helvetica Neue",helvetica,arial,verdana,sans-serif;
        }
        #header {
            background-color: #3C6FA4;
            height: 230px;
            border-bottom-color: #F2F2F2;
            border-bottom-width: 26px;
            border-bottom-style: solid;
        }
        #header img {
            margin: 26px 0px 26px 20px;
        }
        h1 {
            color: #005083;
            margin-top: 0px;
            margin-bottom: 10px;
        }
        .h1 {
            border-bottom-color: #F2F2F2;
            border-bottom-width: 6px;
            border-bottom-style: solid;
        }
        #h1 {
            color: #005083;
        }
        #form {
            margin-top: 60px;
            width: 450px;
            margin-left: auto;
            margin-right: auto;
        }
 
        div.label {
            display: inline-block;
            width: 160px;
        }
        div.field {
            display: inline-block;
            width: 230px;
        }
        div.field input {
            width: 200px;
        }
        div.formfield {
            margin-top: 10px;
            margin-bottom: 10px;
        }
        #formactions {
            padding-left: 160px;
        }
        #formactions button {
            background-color: #005083;
            color: #FFFFFF;
            border: none;
            padding: 5px 0px;
            width: 100px;
            font-size: 14px;
        }
    </style>
  </head>
  <body>
      <div id="header"><img src="static/image/Logo_AZSTBL.jpg" width="356" height="174" /></div>
    <div id="form">
        <div class="h1"><h1>Aanmelden</h1></div>
        <form name="f" action="login" method="post">               
 
            <div class="formfield">
                <div class="label"><label for="username">Gebruikersnaam</label></div><div class="field"><input type="text" id="username" name="username"/></div>
            </div>
            <div class="formfield">
                <div class="label"><label for="password">Wachtwoord</label></div><div class="field"><input type="password" id="password" name="password"/></div>
            </div>
            <div id="formactions">
                <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
                <button type="submit" class="btn">Aanmelden</button>
            </div>
        </form>
    </div>
  </body>
</html>

App

Add index.jsp to /WEB-INF/jsp

this should be the index.html of your extjs application (slighly modified like :)

index.jsp
<!DOCTYPE HTML>
<html manifest="cache.appcache">
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
 
    <title>BoardPortal</title>
 
    <script>
        var MyUser = {
            id: ${login.id},
            firstname: '${login.firstname}',
            lastname: '${login.lastname}',
            email: '${login.email}',
            username: '${login.username}',
            picture: '${login.picture}',
            group: ${login.group}
        };
    </script>
 
 
    <script type="text/javascript">
        var Ext = Ext || {}; // Ext namespace won't be defined yet...
 
        // This function is called by the Microloader after it has performed basic
        // device detection. The results are provided in the "tags" object. You can
        // use these tags here or even add custom tags. These can be used by platform
        // filters in your manifest or by platformConfig expressions in your app.
        //
        Ext.beforeLoad = function (tags) {
            var s = location.search,  // the query string (ex "?foo=1&bar")
                profile;
 
            // For testing look for "?classic" or "?modern" in the URL to override
            // device detection default.
            //
            if (s.match(/\bclassic\b/)) {
                profile = 'classic';
            }
            else if (s.match(/\bmodern\b/)) {
                profile = 'modern';
            }
            else {
                profile = tags.desktop ? 'classic' : 'modern';
                //profile = tags.phone ? 'modern' : 'classic';
            }
 
            Ext.manifest = 'classic';
            //Ext.manifest = profile; // this name must match a build profile name
 
            // This function is called once the manifest is available but before
            // any data is pulled from it.
            //
            //return function (manifest) {
                // peek at / modify the manifest object
            //};
        };
    </script>
 
 
    <!-- The line below must be kept intact for Sencha Cmd to build your application -->
    <script id="microloader" data-app="2f16b4b7-8389-4ed0-91cc-c7a7d705e323" type="text/javascript"> ... Ext.Microloader.run();</script>
 
</head>
<body></body>
</html>

API

Our api is now ready for extjs6, in order to use the filtering, you'll have to modify a store to “remoteFilter: true”.

Static resources (javascript, css, ...)

Don'y use html pages, use jsp like above. simply place the resource in /static/ or any subfolder there

EXT JS 6

A new App

Setup

  1. Download and Install Sencha Cmd 6
  2. Download and unzip the Ext JS 6 SDK (remember the path)

To start a new EXTJS project

  1. Open your terminal or console window and issue these commands *inside* the static folder :
sencha -sdk /path/to/ext6 generate app <APPNAME> <APPNAME>
cd <APPNAME>
sencha app watch

This will generate a skeleton app, sencha app watch will check for any changes made to css or js.

To take over an existing EXTJS project

  • Open your terminal or console window and issue these commands *inside* the app folder :
sencha app install --frameworks ~/Downloads/ExtJS/
sencha app watch

This will allow you to work with the ext project, sencha app watch will check for any changes made to css or js.

To update an existing EXTJS project

  • Open your terminal or console window and issue these commands *inside* the app folder :
sencha app upgrade ~/Downloads/ExtJS/ext-6.6.0/
sencha app watch

Locale package for modern (<6.6.0)

download this & place it in static/extjsproject/ext/modern/ with the name locale

Configuration to be aware of

Time to start changing the application

You can now start editting your application.

Desktop code usually goes into <APPNAME>/classic/src, stylechanges in <APPNAME>/classic/sass
Mobile/Tablet code usually goes into <APPNAME>/modern/src, stylechanges in <APPNAME>/modern/sass

Notes

  • You can only use “classic” extjs classes in the classic folder, & “modern” (touch) classes in the modern folder.
  • Binding doesn't always work like it's supposed to, but it makes the code really neat and you should try it out.
  • Model relations don't always work like they're supposed to, try to avoid them
  • if a ui config doesn't work, it's likely that the name was already taken

Adjusting your build

static/BoardPortal/app.json :

find the mentioning of BUILDCHANGE in the comments to differentiate between a production build & a development build

build for testing

  • change app.json to use index.html (as mentionned in BUILDCHANGE comments)
  • go to static/BoardPortal with terminal/cmd
  • use “sencha app build classic”
  • use “sencha app watch” to deal with all javascript changes

build for production

  • change app.json to use ../index.html (as mentionned in BUILDCHANGE comments)
  • go to static/BoardPortal with terminal/cmd
  • use “sencha app build classic”

Server preparations