This is an old revision of the document!


New Java Web Applications

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
  • Spring (latest → 4.3.9.RELEASE)
  • Spring Security (latest → 4.2.3.RELEASE)
  • Hibernate (latest → 5.2.10.Final)
  • Javamelody (latest, developer tool, used for statistics)
  • ExtJS 6
  • Tomcat 7 or 8 (DOWNLOAD NEWEST FROM THEIR WEBSITE! required for java config, earlier versions supplied with netbeans are bad)
  • Use Java 7 for compiling, I recommend Java 8 for running tomcat (no permgen errors) ;-).

Code examples were taken from Merke or BoardPortal. This documentation is still a work in progress.

Tracking versions

A website which allows you to keep a list of dependencies to watch :
https://www.artifact-listener.org/

Spring Blog https://spring.io/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.

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.

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

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 :

<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>
 
    <profiles>
  <profile>
    <id>java8-doclint-disabled</id>
    <activation>
      <jdk>[1.8,)</jdk>
    </activation>
    <properties>
      <javadoc.opts>-Xdoclint:none</javadoc.opts>
    </properties>
  </profile>
</profiles>
 
    <properties>
        <javax.servletapi>3.0.1</javax.servletapi>
        <javax.servletjspapi>2.2.1</javax.servletjspapi>
 
        <spring.version>4.3.8.RELEASE</spring.version>
        <springsecurity.version>4.2.2.RELEASE</springsecurity.version>
        <mysql.version>5.1.41</mysql.version>
        <dbcp.version>2.1.1</dbcp.version>
        <hibernate.version>5.2.10.Final</hibernate.version>
        <jackson.version>2.8.8</jackson.version>
        <apache.taglibs.version>1.2.5</apache.taglibs.version>
        <log4j.version>2.8.2</log4j.version>
        <commons.lang.version>3.5</commons.lang.version>
        <commons.io.version>2.5</commons.io.version>
        <commons.beanutils.version>1.9.3</commons.beanutils.version>
        <javax.mail.version>1.5.6</javax.mail.version>
        <apache.poi.version>3.16</apache.poi.version>
        <javamelody.version>1.67.0</javamelody.version>
        <findbugs.helper.version>3.0.2</findbugs.helper.version>
 
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 
        <report.plugin.maven.inforeports>2.9</report.plugin.maven.inforeports>
        <report.plugin.maven.jxr>2.5</report.plugin.maven.jxr>
        <report.plugin.maven.javadoc>2.10.4</report.plugin.maven.javadoc>
        <report.plugin.maven.checkstyle>2.17</report.plugin.maven.checkstyle>
        <report.plugin.maven.pmd>3.7</report.plugin.maven.pmd>
        <report.plugin.maven.surefire>2.20</report.plugin.maven.surefire>
        <report.plugin.codehaus.findbugs>3.0.4</report.plugin.codehaus.findbugs>
        <report.plugin.codehaus.taglist>2.4</report.plugin.codehaus.taglist>
        <report.plugin.codehaus.versions>2.3</report.plugin.codehaus.versions>
 
        <build.plugin.maven.site>3.6</build.plugin.maven.site>
        <build.plugin.maven.compiler>3.6.1</build.plugin.maven.compiler>
        <build.plugin.maven.deploy>2.8.2</build.plugin.maven.deploy>
        <build.plugin.maven.clean>3.0.0</build.plugin.maven.clean>
        <build.plugin.maven.install>2.5.2</build.plugin.maven.install>
        <build.plugin.maven.dependency>3.0.0</build.plugin.maven.dependency>
        <build.plugin.maven.war>3.1.0</build.plugin.maven.war>
        <build.plugin.maven.resources>3.0.2</build.plugin.maven.resources>
        <build.plugin.maven.surefire>2.20</build.plugin.maven.surefire>
        <build.plugin.tomcat.maven.tomcat7>2.2</build.plugin.tomcat.maven.tomcat7>
    </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>org.apache.taglibs</groupId>
            <artifactId>taglibs-standard-spec</artifactId>
            <version>${apache.taglibs.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.taglibs</groupId>
            <artifactId>taglibs-standard-impl</artifactId>
            <version>${apache.taglibs.version}</version>
        </dependency>
        <!-- Spring dependencies -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- Spring Security -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>${springsecurity.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>${springsecurity.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-taglibs</artifactId>
            <version>${springsecurity.version}</version>
        </dependency>
        <!-- DBCP (database) -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>${dbcp.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>${commons.lang.version}</version>
        </dependency>
        <!-- Mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <!-- Hibernate -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</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}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
            <version>${jackson.version}</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}</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}</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}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>${log4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-web</artifactId>
            <version>${log4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>${log4j.version}</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>${commons.io.version}</version>
        </dependency>
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>${commons.beanutils.version}</version>
        </dependency>
        <dependency>
            <groupId>com.sun.mail</groupId>
            <artifactId>javax.mail</artifactId>
            <version>${javax.mail.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>${apache.poi.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>${apache.poi.version}</version>
        </dependency>
        <dependency>
            <groupId>net.bull.javamelody</groupId>
            <artifactId>javamelody-core</artifactId>
            <version>${javamelody.version}</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.findbugs</groupId>
            <artifactId>jsr305</artifactId>
            <version>${findbugs.helper.version}</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>
                    <additionalparam>${javadoc.opts}</additionalparam>
                </configuration>
        </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-checkstyle-plugin</artifactId>
                <version>${report.plugin.maven.checkstyle}</version>
                <configuration>
                    <checkstyleRules>
                        <module name="LineLength">
                            <property name="max" value="120"/>
                        </module>
                    </checkstyleRules>
                </configuration>
                <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>
                </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>
        <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>
                <artifactId>maven-deploy-plugin</artifactId>
                <version>${build.plugin.maven.deploy}</version>
            </plugin>
            <plugin>
                <artifactId>maven-clean-plugin</artifactId>
                <version>${build.plugin.maven.clean}</version>
            </plugin>
            <plugin>
                <artifactId>maven-install-plugin</artifactId>
                <version>${build.plugin.maven.install}</version>
            </plugin>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>${build.plugin.tomcat.maven.tomcat7}</version>
                <configuration>
                    <mode>both</mode>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>${build.plugin.maven.dependency}</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>${build.plugin.maven.war}</version>
                <configuration>
                    <useCache>true</useCache>
                </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,
the parent pom ensures all projects use UTF-8 encoding for files & sets some maven plugins.

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.14</version>
        </dependency>

Hibernate search (for textsearching purposes / index)

<dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-search-orm</artifactId>
   <version>5.7.0.Final</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
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

<?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"/>
    <Parameter name="database_catalog" value="applicationname"/>
 
    <Parameter name="searchindexPath" value="/var/mentoringsystems/applicationname/index"/>
 
    <Parameter name="uploadPath" value="/var/mentoringsystems/applicationname"/>
</Context>

web.xml :

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

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

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

MvcConfiguration

package be.mentoringsystems.boardportal.config;
 
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.List;
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.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.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
 
/**
 *
 * @author anthonyarents
 */
@Configuration
@EnableWebMvc
public class MvcConfiguration extends WebMvcConfigurerAdapter {
 
    @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.useJaf(false);
        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);
 
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
        builder.indentOutput(true);
        builder.defaultViewInclusion(true);
        builder.modules(hibernate5Module);
        builder.dateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm")).timeZone("CET");
        converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
 
        final StringHttpMessageConverter stringMessageConverter = new StringHttpMessageConverter(Charset.forName("UTF-8"));
        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/image/**").addResourceLocations("/static/image/").setCachePeriod(2678400);
        // development boardportal
        registry.addResourceHandler("/static/BoardPortal/**").addResourceLocations("/static/BoardPortal/").setCachePeriod(604800);
        // production boardportal
 
        //registry.addResourceHandler("/static/BoardPortal/**").addResourceLocations("/static/BoardPortal/build/production/BoardPortal/").setCachePeriod(604800);
        //registry.addResourceHandler("/BoardPortal/**").addResourceLocations("/static/BoardPortal/build/production/BoardPortal/").setCachePeriod(604800);
        //registry.addResourceHandler("/production/BoardPortal/**").addResourceLocations("/static/BoardPortal/build/production/BoardPortal/").setCachePeriod(604800);
        super.addResourceHandlers(registry);
    }
 
    @Override
    public void configureViewResolvers(final ViewResolverRegistry registry) {
        registry.jsp("/WEB-INF/jsp/", ".jsp");
    }
 
    @Override
    public void addViewControllers(final ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
        registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
        // development boardportal
        registry.addRedirectViewController("/", "/static/BoardPortal/");
    }
 
    @Bean(name = "multipartResolver")
    public StandardServletMultipartResolver multipartResolver() {
        return new StandardServletMultipartResolver();
    }
 
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**");
    }
 
}

DatabaseConfiguration

package be.mentoringsystems.boardportal.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;
 
    @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.jdbc.Driver");
        bds.setMaxIdle(17);
        bds.setMaxTotal(20);
        bds.setMaxWaitMillis(10000);
        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.boardportal.model.db"});
        sessionFactory.setPackagesToScan(new String[]{"be.mentoringsystems.boardportal.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

package be.mentoringsystems.boardportal.config;
 
import be.mentoringsystems.boardportal.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;
 
 
/**
 *
 * @author anthonyarents
 */
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
 
    @Autowired
    private LoginService loginService;
 
    @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
            .formLogin()
                .loginPage("/login").defaultSuccessUrl("/", true).failureUrl("/login?error").permitAll() //login page, redirect tologinrequired page if there was one, permitall is necessary
                .and()
                .logout().logoutUrl("/logout").permitAll() // logout page, permitall is necessary
                .and()
           .authorizeRequests() // request matching with ant
                .antMatchers("/login/**").permitAll() // allows for login to have more get requests (localization)
                .antMatchers("/register").permitAll()
                .antMatchers("/event/**").permitAll()
                .anyRequest().authenticated(); // all remaining requests need login
    }
 
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        Md5PasswordEncoder passwordEncoder = new Md5PasswordEncoder();
        auth.userDetailsService(loginService).passwordEncoder(passwordEncoder);
    }
 
}

Spring Webapplication Initializer

removes web.xml config: the servlet code

package be.mentoringsystems.boardportal.config.initializer;
 
import be.mentoringsystems.boardportal.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(ServletRegistration.Dynamic registration) {
        MultipartConfigElement multipartConfigElement = new MultipartConfigElement("/tmp", parseSize("50MB"), parseSize("70MB"), 0);
        registration.setMultipartConfig(multipartConfigElement);
    }
 
    // convience to make this config a bit more readable...
	private long parseSize(String size) {
		size = size.toUpperCase();
		if (size.endsWith("KB")) {
			return Long.valueOf(size.substring(0, size.length() - 2)) * 1024;
		}
		if (size.endsWith("MB")) {
			return Long.valueOf(size.substring(0, size.length() - 2)) * 1024 * 1024;
		}
		return Long.valueOf(size);
	}
 
}

Spring Security Initializer

removes web.xml config: filterchain

package be.mentoringsystems.boardportal.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

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

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

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

Model - Service - DAO - Presentation

QueryParams

package be.mentoringsystems.merke.model;
 
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>();
 
    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;
    }
 
    @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 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

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(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

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

SortingMap

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

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

Helpers

package be.mentoringsystems.merke.helper;
 
import java.io.Serializable;
import java.util.UUID;
import org.hibernate.HibernateException;
import org.hibernate.engine.SessionImplementor;
 
/**
 *
 * @author anthonyarents
 */
public class UUIDTypeGenerator extends org.hibernate.id.GUIDGenerator {
 
    public UUIDTypeGenerator() {
        super();
    }
 
    @Override
    public Serializable generate(SessionImplementor session, Object obj) throws HibernateException {
        String result = (String) super.generate(session, obj);
        return UUID.fromString(result);
    }
}
package be.mentoringsystems.merke.helper;
 
import java.util.ArrayList;
import java.util.Date;
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 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 Date toDate(final Object obj) {
        if (obj == null) {
            return null;
        }
        if (obj instanceof Date) {
            return (Date) obj;
        }
        return null;
    }
 
    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;
    }
}
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 class 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>”
package be.mentoringsystems.merke.model.db;
 
import be.mentoringsystems.merke.model.dto.View;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonView;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
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.Temporal;
import javax.persistence.TemporalType;
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 Date createdOn = new Date();
    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
    @Temporal(TemporalType.TIMESTAMP)
    public Date getCreatedOn() {
        return (createdOn == null ? null : new Date(createdOn.getTime()));
    }
 
    public void setCreatedOn(Date createdOn) {
        this.createdOn = (createdOn == null ? null : new Date(createdOn.getTime()));
    }
 
    @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

@GenericGenerator(
    name = "uuid",
    strategy = "be.mentoringsystems.helper.UUIDTypeGenerator"
)
@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;

DAO Interface

Now we could use a DAO interface

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

DAO Implementation

Superclass

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());
    }
}

A Hibernate DAO implementation

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);
    }
 
}
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 :-).

Service Interface

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

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(final Login obj);
    Login save(final LoginDTO dto);
    void delete(final Login obj);
    void delete(final UUID id);
    Login getById(final UUID id);
    Login getByUsername(final String login);
    List<Login> getAll(final QueryParams queryParams);
    Long getCount(final QueryParams queryParams);
    PagedListDTO getAllDTO(final QueryParams queryParams);
    Login getCurrentLogin();
    Login getSourceLogin();
}
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(final Vendor obj);
    Vendor save(final VendorDTO dto);
    void delete(final Vendor obj);
    void delete(final UUID id);
    Vendor getById(final UUID id);
    List<Vendor> getAll(final QueryParams queryParams);
    Long getCount(final QueryParams queryParams);
    PagedListDTO getAllDTO(final QueryParams queryParams);
}

Service Implementation

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.dto.LoginDTO;
import be.mentoringsystems.merke.model.dto.PagedListDTO;
import be.mentoringsystems.merke.persistence.LoginDAO;
import be.mentoringsystems.merke.service.LoginService;
import java.io.UnsupportedEncodingException;
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.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.DigestUtils;
import org.springframework.util.StringUtils;
 
/**
 *
 * @author anthonyarents
 */
@Service
@Transactional(propagation = Propagation.SUPPORTS)
public class LoginServiceImpl implements LoginService {
 
    @Autowired
    private transient LoginDAO loginDAO;
    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) {
        Login obj;
        if(dto.getId() == null) {
            obj = new Login();
            LOGGER.info("Creating a new Login");
        } else {
            obj = getById(dto.getId());
            LOGGER.info("Updating existing Login({})", dto.getId());
        }
 
        String dtoPassword = dto.getPassword();
        dto.setPassword(null);
 
        DTOHelper.copyNonNull(dto, obj);
 
        if(StringUtils.hasText(dtoPassword)) obj.setPassword(encryptPassword(dtoPassword));
 
        save(obj);
        LOGGER.info("Login({}) saved", obj.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 PagedListDTO pagedListDTO = new PagedListDTO();
        pagedListDTO.setData(getAll(queryParams));
        pagedListDTO.setTotal(getCount(queryParams));
        return pagedListDTO;
    }
 
    public String encryptPassword(final String password) {
        String result = password;
        try {
            result = DigestUtils.md5DigestAsHex(password.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException ex) {
            LOGGER.error(ex.getMessage(), ex);
        }
        return result;
    }
 
    @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.getGroup() == 1) {
            authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        }
        if(login.getGroup() == 3) {
            authorities.add(new SimpleGrantedAuthority("ROLE_FIREMAN"));
        }
        authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
        return authorities;
    }
 
    @Override
    public Login getCurrentLogin() {
        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;
    }
 
}
package be.mentoringsystems.merke.service.impl;
 
import be.mentoringsystems.merke.helper.DTOHelper;
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 be.mentoringsystems.merke.persistence.VendorDAO;
import be.mentoringsystems.merke.service.VendorService;
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;
    private static final Logger LOGGER = LogManager.getLogger(VendorServiceImpl.class);
 
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void save(Vendor obj) {
        vendorDAO.save(obj);
    }
 
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public Vendor save(VendorDTO dto) {
        Vendor obj;
        if(dto.getId() == null) {
            obj = new Vendor();
            LOGGER.info("Creating a new Vendor");
        } else {
            obj = getById(dto.getId());
            LOGGER.info("Updating existing Vendor({})", dto.getId());
        }
 
        DTOHelper.copyNonNull(dto, obj);
 
        save(obj);
        LOGGER.info("Vendor({}) saved", obj.getId());
        return obj;
    }
 
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void delete(Vendor obj) {
        obj.setDeleted(true);
        save(obj);
        LOGGER.info("Vendor({}) deleted", obj.getId());
    }
 
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void delete(UUID id) {
        final Vendor obj = getById(id);
        delete(obj);
    }
 
    @Override
    @Transactional(readOnly = true)
    public Vendor getById(UUID id) {
        return vendorDAO.getById(id);
    }
 
    @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) {
        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.

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 {};
}

Data Transfer Objects (DTO)

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

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;
 
    public Boolean getActive() {
        return active;
    }
 
    public void setActive(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(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(String username) {
        this.username = username;
    }
 
    public String getPassword() {
        return password;
    }
 
    public void setPassword(final String password) {
        this.password = password;
    }
}
package be.mentoringsystems.merke.model.dto;
 
import java.util.Date;
import java.util.UUID;
 
/**
 *
 * @author anthonyarents
 */
public class VendorDTO {
    private UUID id;
    private UUID parentid;
    private UUID loginId;
    private String title;
    private String title2;
    private String firstname;
    private String lastname;
    private String idnrNationalRegister;
    private String street;
    private String number;
    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 Date birthdate;
    private String permitNumber;
    private String status;
    private String change;
 
    public String getTitle2() {
        return title2;
    }
 
    public void setTitle2(String title2) {
        this.title2 = title2;
    }
 
    public String getChange() {
        return change;
    }
 
    public void setChange(String change) {
        this.change = change;
    }
 
    public String getStatus() {
        return status;
    }
 
    public void setStatus(String status) {
        this.status = status;
    }
 
    public String getPermitNumber() {
        return permitNumber;
    }
 
    public void setPermitNumber(String permitNumber) {
        this.permitNumber = permitNumber;
    }
 
    public UUID getId() {
        return id;
    }
 
    public void setId(UUID id) {
        this.id = id;
    }
 
    public UUID getLoginId() {
        return loginId;
    }
 
    public void setLoginId(UUID loginId) {
        this.loginId = loginId;
    }
 
    public UUID getParentid() {
        return parentid;
    }
 
    public void setParentid(UUID parentid) {
        this.parentid = parentid;
    }
 
    public String getTitle() {
        return title;
    }
 
    public void setTitle(String title) {
        this.title = title;
    }
 
    public String getFirstname() {
        return firstname;
    }
 
    public void setFirstname(String firstname) {
        this.firstname = firstname;
    }
 
    public String getLastname() {
        return lastname;
    }
 
    public void setLastname(String lastname) {
        this.lastname = lastname;
    }
 
    public String getIdnrNationalRegister() {
        return idnrNationalRegister;
    }
 
    public void setIdnrNationalRegister(String idnrNationalRegister) {
        this.idnrNationalRegister = idnrNationalRegister;
    }
 
    public String getStreet() {
        return street;
    }
 
    public void setStreet(String street) {
        this.street = street;
    }
 
    public String getNumber() {
        return number;
    }
 
    public void setNumber(String number) {
        this.number = number;
    }
 
    public String getPostalcode() {
        return postalcode;
    }
 
    public void setPostalcode(String postalcode) {
        this.postalcode = postalcode;
    }
 
    public String getCity() {
        return city;
    }
 
    public void setCity(String city) {
        this.city = city;
    }
 
    public String getCompanyName() {
        return companyName;
    }
 
    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }
 
    public String getCompanyNumber() {
        return companyNumber;
    }
 
    public void setCompanyNumber(String companyNumber) {
        this.companyNumber = companyNumber;
    }
 
    public String getBankaccount() {
        return bankaccount;
    }
 
    public void setBankaccount(String bankaccount) {
        this.bankaccount = bankaccount;
    }
 
    public String getTelephonenumber() {
        return telephonenumber;
    }
 
    public void setTelephonenumber(String telephonenumber) {
        this.telephonenumber = telephonenumber;
    }
 
    public String getEmail() {
        return email;
    }
 
    public void setEmail(String email) {
        this.email = email;
    }
 
    public Date getBirthdate() {
        return (birthdate == null ? null : new Date(birthdate.getTime()));
    }
 
    public void setBirthdate(Date birthdate) {
        this.birthdate = (birthdate == null ? null : new Date(birthdate.getTime()));
    }
 
    public String getMobilenumber() {
        return mobilenumber;
    }
 
    public void setMobilenumber(String mobilenumber) {
        this.mobilenumber = mobilenumber;
    }
}

It's also useful for outgoing data :

package be.mentoringsystems.boardportal.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(Object data) {
        this.data = data;
    }
 
    public List<String> getMessages() {
        return messages;
    }
 
    public void setMessages(List<String> messages) {
        this.messages = messages;
    }
 
    public void addMessage(String message) {
        this.messages.add(message);
    }
 
    public Boolean getSuccess() {
        return success;
    }
 
    public void setSuccess(Boolean success) {
        this.success = success;
    }
 
    public ApiDTO(Object data) {
        this.data = data;
    }
 
    public ApiDTO() {
    }
 
    public static ApiDTO error(List<String> messages) {
        final ApiDTO apiDto = new ApiDTO();
        apiDto.setSuccess(Boolean.FALSE);
        apiDto.setMessages(messages);
        return apiDto;
    }
}

and paging :

package be.mentoringsystems.boardportal.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(new Long(total));
    }
 
}

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.

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.service.LoginService;
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("/logins")
public class LoginController {
 
    @Autowired
    private LoginService loginService;
 
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    @ResponseStatus(HttpStatus.OK)
    public ApiDTO deleteById(@PathVariable UUID id) {
        loginService.delete(id);
        return new ApiDTO();
    }
 
    @JsonView(View.Login.class)
    @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
    @ResponseStatus(HttpStatus.OK)
    public ApiDTO updateById(@PathVariable UUID id, @RequestBody LoginDTO dto) {
        dto.setId(id);
        final Login result = loginService.save(dto);
        return new ApiDTO(result);
    }
 
    @JsonView(View.Login.class)
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @RequestMapping(value = "", method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.CREATED)
    public ApiDTO createNew(@RequestBody LoginDTO dto) {
        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 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(QueryParams queryParams) {
        return loginService.getAllDTO(queryParams);
    }
}
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.

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 org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.util.Throwables;
import org.springframework.http.HttpStatus;
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(ControllerAdvice.class);
 
    @InitBinder("queryParams")
    public void initBinderForQueryParams(WebDataBinder binder) {
        binder.registerCustomEditor(FilterMap.class, new FilterMapEditor());
        binder.registerCustomEditor(SortingMap.class, new SortingMapEditor());
    }
 
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd HH:mm"), true));
    }
 
    @ExceptionHandler(value = Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public ApiDTO exception(Exception exception, WebRequest request) {
        LOGGER.error(exception.getMessage(), exception);
        return ApiDTO.error(Throwables.toStringList(exception));
    }
}

Presentation (Conversion)

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(String text) throws IllegalArgumentException {
        ObjectMapper mapper = new ObjectMapper();
        final FilterMap filterMap = new FilterMap();
        try {
            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);
    }
 
}
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(String text) throws IllegalArgumentException {
        ObjectMapper mapper = new ObjectMapper();
        final SortingMap sortingMap = new SortingMap();
        try {
            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);
    }
 
}

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

package be.mentoringsystems.paypermail.presentation.locale;
 
import be.mentoringsystems.paypermail.model.db.Login;
import be.mentoringsystems.paypermail.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;
 
/**
 *
 * @author anthonyarents
 */
public class CustomLocaleResolver extends CookieLocaleResolver {
 
    @Autowired
    private LoginService loginService;
 
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        final Login login = loginService.getCurrentLogin();
        if(login != null) {
            final Locale locale = new Locale(login.getLanguage()); // can be any other language parameter on login.
            request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME, locale);
            request.setAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME, determineDefaultTimeZone(request));
            return locale;
        } else {
            return super.resolveLocale(request);
        }
    }
 
}

Excel handling

ExportController

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

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);
    }
}
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

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

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.

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/

{
    "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

<%@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 :)

<!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

  1. Open your terminal or console window and issue these commands *inside* the app folder :
sencha app upgrade /path/to/sdk/extjs <APPNAME> <APPNAME>
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.

Locale package for modern

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”

Java

Install a good JDK on your server. for Ubuntu 14, I recommend forcing java 8 on it : http://ubuntuhandbook.org/index.php/2015/01/install-openjdk-8-ubuntu-14-04-12-04-lts/

sudo add-apt-repository ppa:openjdk-r/ppa
sudo apt-get update
sudo apt-get install openjdk-8-jdk

Java 8 makes it a bit easier to manage (they removed permsize)

Tomcat 7

install

sudo apt-get install tomcat7
sudo apt-get install tomcat7-user

create an instance

create a path like /var/tomcat7/servers/
cd /var/tomcat7/servers/

change ports (8080=http port, 8005=control/shutdown port)
sudo tomcat7-instance-create -p 8080 -c 8005 <instancename>
sudo chown -R tomcat7: <instancename>

activate AJP connector + change it's port (default 8009) in
<instancename>/conf/server.xml

Certificates

https://letsencrypt.org/ free certificate provider (certificate is 90 days with auto renewal)
https://certbot.eff.org/ easy AUTOMATIC install

provide for normal domains

sudo ./certbot-auto –apache

update existing certificate by adding www

sudo ./certbot-auto –apache -d cliclapaybymail.be -d www.cliclapaybymail.be

Boardportal

server path : /var/lib/tomcat7/
webapps folder : /var/lib/tomcat7/webapps/

stop : sudo -u tomcat7 /var/lib/tomcat7/bin/shutdown.sh
start : sudo -u tomcat7 /var/lib/tomcat7/bin/startup.sh

context.xml is now ALWAYS located under /var/lib/tomcat7/webapps/BoardPortal/META-INF/context.xml

PS: don't forget to copy context.xml before changing the webapp. You'll lose database settings !