====== New Java Web Applications ====== **End of life sometime after 2020.** Life will be prolonged by major and minor updates to ALL frameworks and libraries\\ (by progressively updating the app whenever a new version comes out for libraries we rely on, we can prevent spending days/weeks/months on a rewrite in the future) If you are making a company project : * You **NEED** nexus access, contact anthony.arents@mentoringsystems.be All new projects have specific requirements in the buildprocess : * Netbeans project * Maven (download latest version for significant speed/memory improvements) * Spring (latest) * Spring Security (latest) * Hibernate (latest 5.4) * Javamelody (latest, developer tool, used for statistics) * ExtJS (frontend latest) or Bootstrap+JQuery * Java 8 or 9, it's compatible with both * Mysql 5.7 (beware the date madness : timestamp(6) & datetime(6)) * Tomcat 8.5 (DOWNLOAD NEWEST FROM THEIR WEBSITE! required for java config, **earlier versions supplied with netbeans are bad**) Code examples were taken from BoardPortal, PayByMail, Merke, SuiteOfTheFuture, Mobi\\ **This documentation is still a work in progress.** ===== Tools ===== * Netbeans 8.2 * Mysql 5.7 : you can use the installer, copy the password when presented ! Mac users : you can find the start & stop in System Preferences : use sequel pro to connect) ===== To remove from existing applications ===== Things that **SHOULD BE** dropped and no longer used : * SimpleDateFormat (use FastDateFormat) * Base64 encoding (unless it's needed for webservices, then we'll be switching to java8 base64 utils) * iText (would be better to switch to apache pdfbox... unless a license is available) * javacsv (can be done with jackson library now) * Apache httpclient (can probably be easily replaced by something easier to use) * commons fileupload (no longer needed) * Dozer (wish we never used this slow thing, got beaten by Mario for a reason) * Joda time (obsolete with java8) ====== Tracking versions ====== ===== Versions plugin ===== mvn site -> generated version reports \\ ===== Spring Blog ===== 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 -> use new JAVA8 date. ====== Maven site usage ====== Please use the maven site ! It will calculate PMD + Findbugs for you.\\ Just rightclick the project : "Run Maven" -> "Goals ..." -> type "site" & save as "site".\\ You'll be able to run it again with "Run Maven" -> "site".\\ \\ In the generated output, there's a link : "View generated site". You can use this link when Maven is finished.\\ \\ You **should** solve all Findbugs & PMD errors. (except maybe jaxws & hibernate packages) ====== Package structure : ====== ''be.mentoringsystems.applicationname.config'' Contains Configuration classes used to configure the application ''be.mentoringsystems.applicationname.config.initializer'' Contains 2 classes: one to create the servlet and one to initialize spring security ''be.mentoringsystems.applicationname.dao'' Contains DAO interfaces, naming ObjectnameDAO ''be.mentoringsystems.applicationname.dao.hibernate'' Contains DAO implementations, naming ObjectnameDAOImpl, extends HibernateDaoSupport, implements ObjectnameDAO ''be.mentoringsystems.applicationname.dao.importer'' Contains importer interfaces ''be.mentoringsystems.applicationname.dao.importer.excel'' Contains excel importer interfaces ''be.mentoringsystems.applicationname.dao.importer.csv'' Contains csv importer interfaces ''be.mentoringsystems.applicationname.model'' Contains helper models which may be used throughout the application (for example QueryParams) ''be.mentoringsystems.applicationname.model.db'' Contains models which will be stored in the database, naming Ojectname ''be.mentoringsystems.applicationname.model.dto'' Contains models which will be intermediaries for the Controller interfaces (forms sent to server end up in a dto which are then translated to models in the service api) ''be.mentoringsystems.applicationname.presentation'' Contains viewcontrollers, Restcontrollers have their own package to make it easier to find them. ''be.mentoringsystems.applicationname.presentation.conversion'' Contains convertors for deserializing (spring). ''be.mentoringsystems.applicationname.presentation.excel'' Contains excel views using apache poi ''be.mentoringsystems.applicationname.presentation.rest'' Contains controllers which will be accessed via Rest ''be.mentoringsystems.applicationname.presentation.serializer'' Contains Jackson serializers ''be.mentoringsystems.applicationname.service'' Contains Service interfaces, naming ObjectnameService ''be.mentoringsystems.applicationname.service.impl'' Contains Service implementations, naming ObjectnameServiceImpl, implements ObjectnameService, uses ObjectnameDAO & Transactional annotations ''be.mentoringsystems.applicationname.tasks'' Contains ScheduledTasks class which has several automated tasks ===== German Projects ===== ''de.jcpis.applicationname'' see above for details. ====== How to start : ====== Start with creating mocks & database model Create a new project : ''New Project -> Maven -> Web Application'' ===== POM : ===== Change the POM (configuration files -> ''pom.xml'') to this : 4.0.0 be.mentoringsystems Merke 1.0-SNAPSHOT war Merke 4.0.1 2.3.3 3.0.0 5.2.0.RELEASE 5.2.0.RELEASE 8.0.17 2.7.0 5.4.6.Final 6.0.17.Final 2.10.0 1.2.5 2.12.1 3.9 1.8 2.6 1.9.4 1.6.2 4.1.0 1.79.0 3.0.2 1.12.1 1.4.0 3.8.3 2.12.0 4.2 1.8 1.8 UTF-8 3.0.0 3.0.0 3.1.1 3.1.0 8.25 checkstyle.xml 3.12.0 6.18.0 3.0.0-M3 3.0.5 2.4 2.7 3.8.2 3.8.1 3.0.0-M1 3.1.0 3.0.0-M1 3.1.1 3.2.3 3.1.0 3.0.0-M3 1.8 3.1.1 2.5.3 javax.servlet javax.servlet-api ${javax.servletapi} provided javax.servlet.jsp javax.servlet.jsp-api ${javax.servletjspapi} provided javax.el javax.el-api ${javax.elapi} provided org.apache.taglibs taglibs-standard-spec ${apache.taglibs} org.apache.taglibs taglibs-standard-impl ${apache.taglibs} org.im4java im4java ${im4java} org.springframework spring-core ${spring} org.springframework spring-web ${spring} org.springframework spring-webmvc ${spring} org.springframework spring-orm ${spring} org.springframework spring-context-support ${spring} org.springframework.security spring-security-web ${springsecurity} org.springframework.security spring-security-config ${springsecurity} org.springframework.security spring-security-taglibs ${springsecurity} org.apache.commons commons-dbcp2 ${dbcp} org.apache.commons commons-lang3 ${commons.lang} org.apache.commons commons-text ${commons.text} mysql mysql-connector-java ${mysql} org.hibernate hibernate-core ${hibernate} com.fasterxml classmate org.hibernate hibernate-validator ${hibernate.validator} org.jsoup jsoup ${jsoup} com.fasterxml.jackson.core jackson-core ${jackson} com.fasterxml.jackson.core jackson-databind ${jackson} com.fasterxml.jackson.core jackson-annotations com.fasterxml.jackson.core jackson-annotations ${jackson} com.fasterxml.jackson.dataformat jackson-dataformat-xml ${jackson} com.fasterxml.jackson.core jackson-annotations com.fasterxml.jackson.dataformat jackson-dataformat-csv ${jackson} com.fasterxml.jackson.core jackson-annotations com.fasterxml.jackson.datatype jackson-datatype-jsr310 ${jackson} com.fasterxml.jackson.core jackson-annotations com.fasterxml.jackson.datatype jackson-datatype-hibernate5 ${jackson} com.fasterxml.jackson.core jackson-annotations org.apache.logging.log4j log4j-core ${log4j} org.apache.logging.log4j log4j-api ${log4j} org.apache.logging.log4j log4j-web ${log4j} org.apache.logging.log4j log4j-slf4j-impl ${log4j} commons-io commons-io ${commons.io} commons-beanutils commons-beanutils ${commons.beanutils} com.sun.mail javax.mail ${javax.mail} org.apache.poi poi ${apache.poi} commons-codec commons-codec org.apache.poi poi-ooxml ${apache.poi} net.bull.javamelody javamelody-core ${javamelody} com.google.code.findbugs jsr305 ${findbugs.helper} com.auth0 java-jwt ${auth0.jwt} com.drewnoakes metadata-extractor ${metadataextractor} org.apache.maven.plugins maven-project-info-reports-plugin ${report.plugin.maven.inforeports} org.apache.maven.plugins maven-jxr-plugin ${report.plugin.maven.jxr} org.apache.maven.plugins maven-surefire-report-plugin ${report.plugin.maven.surefire} org.apache.maven.plugins maven-javadoc-plugin ${report.plugin.maven.javadoc} none org.apache.maven.plugins maven-checkstyle-plugin ${report.plugin.maven.checkstyle} checkstyle org.codehaus.mojo taglist-maven-plugin ${report.plugin.codehaus.taglist} org.apache.maven.plugins maven-pmd-plugin ${report.plugin.maven.pmd} false true org.codehaus.mojo findbugs-maven-plugin ${report.plugin.codehaus.findbugs} org.codehaus.mojo versions-maven-plugin ${report.plugin.codehaus.versions} dependency-updates-report plugin-updates-report property-updates-report org.apache.maven.plugins maven-pmd-plugin ${report.plugin.maven.pmd} net.sourceforge.pmd pmd-core ${report.plugin.maven.pmd.pmdversion} net.sourceforge.pmd pmd-java ${report.plugin.maven.pmd.pmdversion} net.sourceforge.pmd pmd-javascript ${report.plugin.maven.pmd.pmdversion} net.sourceforge.pmd pmd-jsp ${report.plugin.maven.pmd.pmdversion} org.apache.maven.plugins maven-checkstyle-plugin ${report.plugin.maven.checkstyle} com.puppycrawl.tools checkstyle ${report.plugin.maven.checkstyle.checkstyleversion} org.apache.maven.plugins maven-antrun-plugin ${build.plugin.maven.antrun} org.apache.maven.plugins maven-assembly-plugin ${build.plugin.maven.assembly} org.apache.maven.plugins maven-release-plugin ${build.plugin.maven.release} org.apache.maven.plugins maven-dependency-plugin ${build.plugin.maven.dependency} org.apache.maven.plugins maven-site-plugin ${build.plugin.maven.site} org.apache.maven.plugins maven-compiler-plugin ${build.plugin.maven.compiler} ${project.build.sourceEncoding} ${maven.compiler.source} ${maven.compiler.target} org.apache.maven.plugins maven-deploy-plugin ${build.plugin.maven.deploy} org.apache.maven.plugins maven-clean-plugin ${build.plugin.maven.clean} org.apache.maven.plugins maven-install-plugin ${build.plugin.maven.install} org.apache.maven.plugins maven-war-plugin ${build.plugin.maven.war} false static/Merke/** org.apache.maven.plugins maven-resources-plugin ${build.plugin.maven.resources} ${project.build.sourceEncoding} org.apache.maven.plugins maven-surefire-plugin ${build.plugin.maven.surefire} ${skipTests} -XX:-UseSplitVerifier This will set most of your dependencies straight, \\ Be sure to check the graph to solve the dependency conflicts. \\ The parent pom ensures all projects use UTF-8 encoding for files & sets some maven plugins. ==== Checkstyle configuration ==== This xml file is based on sun_checks (removed some annoying ones). Place this in the **ROOT** folder of your webapp (next to pom.xml) as checkstyle.xml ==== Possible extras ==== JSTL (Java Standard Tag Libraries), using an implementation which doesn't potentially break your server. org.apache.taglibs taglibs-standard-spec 1.2.5 org.apache.taglibs taglibs-standard-impl 1.2.5 Apache Tika (for textextraction purposes) org.apache.tika tika-parsers 1.22 Hibernate search (for textsearching purposes / index) org.hibernate hibernate-search-orm 5.11.3.Final Hibernate validator for java < 8 (for validation purposes, also combine with jsoup to scan for scripts in html) org.hibernate hibernate-validator 5.4.3.Final Hibernate validator for java 8+ (for validation purposes, also combine with jsoup to scan for scripts in html) org.hibernate hibernate-validator 6.0.17.Final JSoup for scanning html org.jsoup jsoup 1.12.1 ===== context.xml : ===== We're going to set some properties in META-INF/context.xml \\ if path = "''/''" it will deploy locally to your server root. To get the same result on a live server, you need to rename the war to ''ROOT.war''. \\ if path = "''/applicationname''" it will deploy locally to /applicationname. To get the same result on a live server, you need to rename the war to ''applicationname.war''.\\ \\ **webAppRootKey**: an identifier for the applciation, give it the value of your applicationname \\ **database_user**: make a database user like ''applicationname_user'', you can make the same one on the live server (with same password).\\ **database_catalog**: applicationname\\ **database_jdbc**: notice extra options useSSL (false) and sendFractionalSeconds (false) for compatibility with mysql 5.7\\ **searchindexPath**: remove entry if not using hibernate search, change applicationname otherwise.\\ **uploadPath**: change applicationname, don't forget to create the path on server + locally. also make sure to ''chmod -R 777'' ===== web.xml : ===== Now we need the following in our ''WEB-INF/web.xml'' \\ Don't forget to change applicationname.\\ applicationname 30 ===== 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.merke.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; /** * * @author anthonyarents */ @Configuration @ComponentScan({ "be.mentoringsystems.merke.*" }) @EnableScheduling @Import({MvcConfiguration.class, DatabaseConfiguration.class, SecurityConfiguration.class, MessageConfiguration.class, MailConfiguration.class, AsyncConfiguration.class}) public class AppConfiguration { } ==== MvcConfiguration ==== package be.mentoringsystems.merke.config; import be.mentoringsystems.merke.presentation.locale.CustomLocaleResolver; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import java.nio.charset.Charset; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Locale; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.http.MediaType; import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import org.springframework.web.multipart.support.StandardServletMultipartResolver; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.ViewResolverRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; /** * * @author anthonyarents */ @Configuration @EnableWebMvc public class MvcConfiguration implements WebMvcConfigurer { private static final int COOKIE_MAX_AGE = 4800; private static final int CACHEPERIOD_IMAGES = 2678400; private static final int CACHEPERIOD_JS = 604800; @Value("#{contextParameters.development}") private Boolean development; @Override public void configureDefaultServletHandling(final DefaultServletHandlerConfigurer configurer) { configurer.enable(); } @Override public void configureContentNegotiation(final ContentNegotiationConfigurer configurer) { configurer.favorPathExtension(false).favorParameter(true); configurer.defaultContentType(MediaType.TEXT_PLAIN); configurer.useRegisteredExtensionsOnly(true); configurer.mediaType("txt", MediaType.TEXT_PLAIN); configurer.mediaType("json", MediaType.APPLICATION_JSON); configurer.mediaType("xml", MediaType.APPLICATION_XML); configurer.mediaType("html", MediaType.TEXT_HTML); } @Override public void configureMessageConverters(final List> converters) { final Hibernate5Module hibernate5Module = new Hibernate5Module(); hibernate5Module.configure(Hibernate5Module.Feature.FORCE_LAZY_LOADING, false); hibernate5Module.configure(Hibernate5Module.Feature.USE_TRANSIENT_ANNOTATION, false); final JavaTimeModule javaTimeModule = new JavaTimeModule(); final DateTimeFormatter localDateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(localDateTimeFormatter)); javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(localDateTimeFormatter)); final Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder(); builder.indentOutput(true); builder.defaultViewInclusion(true); builder.modules(hibernate5Module, javaTimeModule); builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); converters.add(new MappingJackson2HttpMessageConverter(builder.build())); final StringHttpMessageConverter stringMessageConverter = new StringHttpMessageConverter(Charset.forName("UTF-8")); stringMessageConverter.setWriteAcceptCharset(false); converters.add(stringMessageConverter); converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build())); converters.add(new ByteArrayHttpMessageConverter()); } @Override public void addResourceHandlers(final ResourceHandlerRegistry registry) { // images registry.addResourceHandler("/static/favicon/**").addResourceLocations("/static/favicon/").setCachePeriod(CACHEPERIOD_IMAGES); registry.addResourceHandler("/static/**").addResourceLocations("/static/").setCachePeriod(604800); if (Boolean.TRUE.equals(development)) { // development registry.addResourceHandler("/static/Merke/**").addResourceLocations("/static/Merke/").setCachePeriod(CACHEPERIOD_JS); registry.addResourceHandler("/staticresources/**").addResourceLocations("/staticresources/").setCachePeriod(604800); registry.addResourceHandler("/build/classic/resources/**").addResourceLocations("/build/classic/resources/").setCachePeriod(604800); registry.addResourceHandler("/build/modern/resources/**").addResourceLocations("/build/modern/resources/").setCachePeriod(604800); registry.addResourceHandler("/build/resources/**").addResourceLocations("/build/resources/").setCachePeriod(604800); registry.addResourceHandler("/**").addResourceLocations("/static/Merke/").setCachePeriod(604800); } else { // production registry.addResourceHandler("/static/Merke/**").addResourceLocations("/static/Merke/").setCachePeriod(CACHEPERIOD_JS); registry.addResourceHandler("/staticresources/**").addResourceLocations("/staticresources/").setCachePeriod(604800); registry.addResourceHandler("/**").addResourceLocations("/build/").setCachePeriod(604800); } } @Override public void configureViewResolvers(final ViewResolverRegistry registry) { registry.jsp("/WEB-INF/jsp/", ".jsp"); } @Override public void addViewControllers(final ViewControllerRegistry registry) { registry.addViewController("/").setViewName("forward:/index.html"); registry.addViewController("/login").setViewName("redirect:/"); registry.addViewController("/favicon.ico").setViewName("forward:/static/favicon/favicon.ico"); registry.setOrder(Ordered.HIGHEST_PRECEDENCE); } @Bean public LocaleResolver localeResolver() { CustomLocaleResolver resolver = new CustomLocaleResolver(); resolver.setDefaultLocale(new Locale("nl")); resolver.setCookieName("myLocaleCookie"); resolver.setCookieMaxAge(COOKIE_MAX_AGE); return resolver; } @Bean public LocaleChangeInterceptor localeChangeInterceptor() { LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor(); interceptor.setParamName("mylocale"); return interceptor; } @Override public void addInterceptors(final InterceptorRegistry registry) { registry.addInterceptor(localeChangeInterceptor()); } @Bean(name = "multipartResolver") public StandardServletMultipartResolver multipartResolver() { return new StandardServletMultipartResolver(); } @Override public void addCorsMappings(final CorsRegistry registry) { registry.addMapping("/**"); } } ==== DatabaseConfiguration ==== package be.mentoringsystems.merke.config; import java.util.Properties; import javax.sql.DataSource; import org.apache.commons.dbcp2.BasicDataSource; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.orm.hibernate5.HibernateTransactionManager; import org.springframework.orm.hibernate5.LocalSessionFactoryBean; import org.springframework.transaction.annotation.EnableTransactionManagement; /** * * @author anthonyarents */ @Configuration @EnableTransactionManagement public class DatabaseConfiguration { @Value("#{contextParameters.database_catalog}") private String dbName; @Value("#{contextParameters.database_user}") private String dbUser; @Value("#{contextParameters.database_password}") private String dbPass; @Value("#{contextParameters.database_jdbc}") private String dbJdbc; @Value("#{contextParameters.searchindexPath}") private String searchindexPath; private static final int DSPOOL_MAX_IDLE = 17; private static final int DSPOOL_MAX_TOTAL = 20; private static final int DSPOOL_MAX_WAIT_MILLIS = 10000; @Bean public DataSource dataSource() { final BasicDataSource bds = new BasicDataSource(); bds.setUsername(dbUser); bds.setPassword(dbPass); bds.setDefaultCatalog(dbName); bds.setUrl(dbJdbc); bds.setDriverClassName("com.mysql.cj.jdbc.Driver"); bds.setMaxIdle(DSPOOL_MAX_IDLE); bds.setMaxTotal(DSPOOL_MAX_TOTAL); bds.setMaxWaitMillis(DSPOOL_MAX_WAIT_MILLIS); bds.setValidationQuery("SELECT 1;"); bds.setTestOnBorrow(true); bds.setRemoveAbandonedOnBorrow(true); return bds; } private Properties getHibernateProperties() { Properties properties = new Properties(); properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect"); properties.put("hibernate.show_sql", false); properties.put("hibernate.format_sql", false); properties.put("hibernate.connection.useUnicode", true); properties.put("hibernate.connection.characterEncoding", "utf8"); properties.put("hibernate.connection.charSet", "utf8"); properties.put("hibernate.search.default.directory_provider", "filesystem"); properties.put("hibernate.search.default.indexBase", searchindexPath); return properties; } @Bean public LocalSessionFactoryBean sessionFactory() { LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean(); sessionFactory.setDataSource(dataSource()); sessionFactory.setAnnotatedPackages(new String[]{"be.mentoringsystems.merke.model.db"}); sessionFactory.setPackagesToScan(new String[]{"be.mentoringsystems.merke.model.db"}); sessionFactory.setHibernateProperties(getHibernateProperties()); return sessionFactory; } @Bean @Autowired public HibernateTransactionManager transactionManager(final SessionFactory s) { HibernateTransactionManager txManager = new HibernateTransactionManager(); txManager.setSessionFactory(s); return txManager; } } ==== SecurityConfiguration ==== package be.mentoringsystems.merke.config; import be.mentoringsystems.merke.security.JWTAuthenticationFilter; import be.mentoringsystems.merke.service.LoginService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.switchuser.SwitchUserFilter; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; /** * * @author anthonyarents */ @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired private LoginService loginService; @Autowired private JWTAuthenticationFilter jwtAuthenticationFilter; @Override public void configure(final WebSecurity web) { web.ignoring().antMatchers("/static/**", "/classic/**", "/app/**", "/production/**", "/Merke/**"); } @Override protected void configure(final HttpSecurity http) throws Exception { // temporary http.csrf().disable(); http.headers().frameOptions().sameOrigin(); http.userDetailsService(loginService); http .addFilter(switchUserFilter()) .addFilterAfter(jwtAuthenticationFilter, BasicAuthenticationFilter.class) //.formLogin() //.loginPage("/login").defaultSuccessUrl("/", false).failureUrl("/login?error").permitAll() //login page, redirect tologinrequired page if there was one, permitall is necessary //.and() .logout().logoutUrl("/logout").logoutSuccessUrl("/").permitAll() // logout page, permitall is necessary .and() .authorizeRequests() // request matching with ant .antMatchers("/**").permitAll() .antMatchers("/login/**").permitAll() .antMatchers("/logins/authenticate").permitAll() .antMatchers("/register").permitAll() .antMatchers("/categories").permitAll() .antMatchers("/venues").permitAll() .antMatchers("/privateVenues").permitAll() .antMatchers("/recoverPassword").anonymous() .antMatchers("/passwordrecovery").anonymous() .antMatchers("/switchuser").hasRole("MSADMIN") .anyRequest().authenticated(); // all remaining requests need login } @Bean(name = "switchUserFilter") public SwitchUserFilter switchUserFilter() { final SwitchUserFilter filter = new SwitchUserFilter(); filter.setUserDetailsService(loginService); filter.setExitUserUrl("/exituser"); filter.setSwitchUserUrl("/switchuser"); filter.setTargetUrl("/"); filter.setUsernameParameter("username"); return filter; } @Bean public PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } @Override protected void configure(final AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(loginService).passwordEncoder(passwordEncoder()); } } ==== Spring Webapplication Initializer ==== removes web.xml config: the servlet code package be.trustandchain.mobi.config.initializer; import be.mentoringsystems.merke.config.AppConfiguration; import javax.servlet.MultipartConfigElement; import javax.servlet.ServletRegistration; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; /** * * @author anthonyarents */ public class SpringConfigurationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class[] getRootConfigClasses() { return new Class[]{AppConfiguration.class}; } @Override protected Class[] getServletConfigClasses() { return null; } @Override protected String[] getServletMappings() { return new String[]{"/"}; } @Override protected void customizeRegistration(final ServletRegistration.Dynamic registration) { MultipartConfigElement multipartConfigElement = new MultipartConfigElement("/tmp", mb(50), mb(70), 0); registration.setMultipartConfig(multipartConfigElement); } private long mb(final long nr) { // 1024 * 1024 return nr * 1048576; } } ==== Spring Security Initializer ==== removes web.xml config: filterchain package be.mentoringsystems.merke.config.initializer; import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; /** * * @author anthonyarents */ public class SpringSecurityInitializer extends AbstractSecurityWebApplicationInitializer { } ===== Optional Java Configuration classes ===== These are examples of extra configuration. The class needs to be added to AppConfiguration's @Import ! ==== Mail configuration ==== used for configuring a javamailer 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.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.annotation.AsyncConfigurerSupport; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; /** * * @author anthonyarents */ @Configuration @EnableAsync public class AsyncConfiguration extends AsyncConfigurerSupport { @Bean public TaskScheduler taskScheduler() { return new ConcurrentTaskScheduler(); } @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(2); executor.setMaxPoolSize(2); executor.setQueueCapacity(100); executor.setThreadNamePrefix("Async-"); executor.setAwaitTerminationSeconds(10); executor.initialize(); return executor; } } ==== Conditional configuration ==== Sometimes, we want certain beans to be used in production but not in test, activate blockchain or disable it, ...\\ We can do this by implementing Condition, in this condition we check the contextParameters, the same code can be used to check \\ environment variables etc. package be.trustandchain.mobi.condition; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; /** * * @author anthonyarents */ public class BlockchainUsageCondition implements Condition { @Override public boolean matches(final ConditionContext cc, final AnnotatedTypeMetadata atm) { String value = cc.getEnvironment().getProperty("use_blockchain"); return "true".equals(value); } } \\ Classes annotated with @Conditional will only be in effect IF the condition returns true :\\ import org.springframework.context.annotation.Conditional; @Repository @Conditional(BlockchainUsageCondition.class) public class CommentDAOImpl implements CommentDAO { ... } ====== Database creation ====== Create a database WITH utf8-bin (case sensitive) CREATE TABLE `Login` ( `id` binary(36) NOT NULL, `firstname` varchar(200) COLLATE utf8_bin NOT NULL, `lastname` varchar(200) COLLATE utf8_bin NOT NULL, `email` varchar(400) COLLATE utf8_bin NOT NULL, `password` binary(60) NOT NULL, `group` int(1) NOT NULL, `username` varchar(200) COLLATE utf8_bin NOT NULL, `deleted` char(1) COLLATE utf8_bin NOT NULL DEFAULT 'N', `active` char(1) COLLATE utf8_bin NOT NULL DEFAULT 'Y', `createdOn` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; ALTER TABLE `Login` ADD PRIMARY KEY (`id`), ADD KEY `username` (`username`); ====== Model - Service - DAO - Presentation ====== ===== QueryParams ===== package be.mentoringsystems.merke.model; import be.mentoringsystems.merke.helper.ConversionHelper; import com.fasterxml.jackson.annotation.JsonAnySetter; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang3.StringUtils; /** * * @author anthonyarents */ public class QueryParams { private int start = 0; private int limit = 0; private FilterMap filter = new FilterMap(); private SortingMap sort = new SortingMap(); private List initializeExtra = new ArrayList(); // return message can be used to return statusses like "data generated" to other methods. private String returnmessage; public String getReturnmessage() { return returnmessage; } public void setReturnmessage(final String returnmessage) { this.returnmessage = returnmessage; } public String getLoadConfig() { return ConversionHelper.toString(this.getFilterValue("loadconfig")); } public void setLoadConfig(final String val) { this.addFilter("loadconfig", val); } public List getInitializeExtra() { return initializeExtra; } public void setInitializeExtra(final List initializeExtra) { this.initializeExtra = initializeExtra; } public void addInitializeExtra(final String property) { this.initializeExtra.add(property); } public FilterMap getFilter() { return filter; } public void setFilter(final FilterMap filter) { this.filter = filter; } public void setSearch(final String search) { this.addFilter("search", search); } @JsonAnySetter public void addFilter(final String property, final Object value) { if (!StringUtils.isEmpty(property) && !StringUtils.startsWith(property, "_")) { Filter filter = getFilter(property); if (filter == null) { filter = new Filter(); filter.setProperty(property); filter.setValue(value); this.filter.addFilter(filter); } else { filter.setValue(value); } } } public void addSorting(final String property, final String direction) { if (!StringUtils.isEmpty(property)) { Sorting sorting = new Sorting(); sorting.setProperty(property); sorting.setDirection(direction); getSort().getSortings().add(sorting); } } public Filter getFilter(final String property) { if (this.getFilter() == null) { return null; } else { return this.filter.getFilter(property); } } public Object getFilterValue(final String property) { if (this.getFilter() == null) { return null; } else { return this.getFilter().getValue(property); } } public int getLimit() { return limit; } public void setLimit(final int limit) { this.limit = limit; } public SortingMap getSort() { return sort; } public void setSort(final SortingMap sort) { this.sort = sort; } public int getStart() { return start; } public void setStart(final int start) { this.start = start; } } ==== FilterMap ==== 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 filters = new ArrayList(); private Map map = new HashMap(); public List getFilters() { return filters; } public void setFilters(final List filters) { this.filters = filters; } public void addFilter(final Filter filter) { this.filters.add(filter); this.map.put(filter.getProperty(), filter); } public Map getMap() { return map; } public void setMap(final Map 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(final String property) { this.property = property; } public Object getValue() { return value; } public void setValue(final 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 sortings = new ArrayList<>(); public List getSortings() { return sortings; } public void setSortings(final List 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(final String direction) { this.direction = direction; } public String getProperty() { return property; } public void setProperty(final String property) { this.property = property; } } ===== Helpers ===== package be.mentoringsystems.merke.helper; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.UUID; import javax.annotation.Nullable; import org.apache.commons.lang3.math.NumberUtils; /** * * @author anthonyarents */ public final class ConversionHelper { private static final DateTimeFormatter LOCALDATEFORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); private static final DateTimeFormatter LOCALDATETIMEFORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private ConversionHelper() { } @SuppressWarnings("unchecked") public static List toObjectList(final Object obj) { List result = new ArrayList(); if (obj == null) { return result; } if (obj instanceof List) { return (List) 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 toStringList(final Object obj) { List result = new ArrayList(); if (obj == null) { return result; } if (obj instanceof List) { return (List) 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 toIntegerList(final Object obj) { List result = new ArrayList(); if (obj == null) { return result; } if (obj instanceof List) { return (List) 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 toUUIDList(final Object obj) { List result = new ArrayList(); if (obj == null) { return result; } List start = toObjectList(obj); for (Object o : start) { UUID u = toUUID(o); if (u != null) { result.add(u); } } return result; } public static UUID toUUID(final Object obj) { if (obj == null) { return null; } if (obj instanceof UUID) { return (UUID) obj; } return UUID.fromString(toString(obj)); } public static String toString(final Object obj) { if (obj == null) { return null; } if (obj instanceof String) { return (String) obj; } return obj.toString(); } public static LocalDate toLocalDate(final Object obj) { if (obj == null) { return null; } if (obj instanceof LocalDate) { return (LocalDate) obj; } String dateString = toString(obj); if (dateString == null) { return null; } else { return (LocalDate) LOCALDATEFORMATTER.parse(dateString); } } public static LocalDateTime toLocalDateTime(final Object obj) { if (obj == null) { return null; } if (obj instanceof LocalDateTime) { return (LocalDateTime) obj; } String dateString = toString(obj); if (dateString == null) { return null; } else { return (LocalDateTime) LOCALDATETIMEFORMATTER.parse(dateString); } } public static Integer toInteger(final Object obj) { if (obj == null) { return null; } if (obj instanceof Integer) { return (Integer) obj; } return NumberUtils.toInt(toString(obj)); } @Nullable public static Boolean toBoolean(final Object obj) { if (obj == null) { return null; } if (obj instanceof Boolean) { return (Boolean) obj; } String objString = toString(obj); if ("1".equals(objString) || "true".equals(objString)) { return Boolean.TRUE; } return Boolean.FALSE; } } package be.mentoringsystems.merke.helper; import java.lang.reflect.InvocationTargetException; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.beanutils.PropertyUtils; /** * * @author anthonyarents */ public final class DTOHelper { private DTOHelper() { } public static void copyNonNull(final Object source, final Object target) { try { Map sourceValues = PropertyUtils.describe(source); Collection 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 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 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." ==== UUID Id ==== package be.mentoringsystems.merke.model.db; import be.mentoringsystems.merke.model.dto.View; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonView; import java.io.Serializable; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.UUID; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Transient; import org.hibernate.annotations.Type; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; /** * * @author anthonyarents */ @Entity public class Login implements UserDetails, Serializable { private static final long serialVersionUID = -5435867605493271271L; private UUID id; private String firstname; private String lastname; private String email; private String password; private Integer group = 2; private String username; private boolean deleted = false; private boolean active = false; private LocalDateTime createdOn = LocalDateTime.now(); private List authorities = new ArrayList(); @JsonIgnore @Column @Type(type = "yes_no") public boolean isDeleted() { return deleted; } public void setDeleted(boolean deleted) { this.deleted = deleted; } @JsonView(View.Public.class) @Id @Column @GeneratedValue( generator = "uuid" ) @Type(type = "uuid-char") public UUID getId() { return id; } public void setId(final UUID id) { this.id = id; } @JsonView(View.Login.class) @Column public LocalDateTime getCreatedOn() { return createdOn; } public void setCreatedOn(final LocalDateTime createdOn) { this.createdOn = createdOn; } @JsonView(View.Login.class) @Column public String getEmail() { return email; } public void setEmail(final String email) { this.email = email; } @JsonView(View.Public.class) @Column public String getFirstname() { return firstname; } public void setFirstname(final String firstname) { this.firstname = firstname; } @JsonView(View.Login.class) @Column(name="`group`", nullable = false) public Integer getGroup() { return group; } public void setGroup(Integer group) { this.group = group; } @JsonView(View.Public.class) @Column public String getLastname() { return lastname; } public void setLastname(final String lastname) { this.lastname = lastname; } @JsonView(View.Login.class) @Column @Override public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } @JsonIgnore @Column(nullable = false) @Override public String getPassword() { return password; } public void setPassword(final String password) { this.password = password; } @JsonView(View.Login.class) @Column @Type(type = "yes_no") public boolean isActive() { return active; } public void setActive(boolean active) { this.active = active; } @JsonIgnore @Override @Transient public Collection getAuthorities() { return authorities; } public void setAuthorities(List 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 = "org.hibernate.id.UUIDGenerator", parameters = { @Parameter( name = "uuid_gen_strategy_class", value = "org.hibernate.id.uuid.CustomVersionOneStrategy" ) } ) @FilterDef(name = "deletedObjectsFilter", defaultCondition = "deleted = 'N'") package be.mentoringsystems.merke.model.db; import org.hibernate.annotations.FilterDef; import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.Parameter; ==== Integer ID ==== package de.jcpis.analysestats.model.db; import java.io.Serializable; import java.util.UUID; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import org.hibernate.annotations.Type; /** * * @author anthonyarents */ @Entity public class Context implements Serializable { private static final long serialVersionUID = -7951893343401921083L; private Integer id; private Integer serverId; private Server server; private UUID contextId; private UUID parentContextId; private String name; private Integer beraterCount; private Integer mandantCount; private Integer assetCount; private Integer syncCount; private boolean hidden = false; @Id @Column @GeneratedValue(strategy = GenerationType.IDENTITY) public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } @Column(insertable = false, updatable = false) public Integer getServerId() { return serverId; } public void setServerId(Integer serverId) { this.serverId = serverId; } @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "serverId") public Server getServer() { return server; } public void setServer(Server server) { this.server = server; } @Column public String getName() { return name; } public void setName(String name) { this.name = name; } @Column public Integer getBeraterCount() { return beraterCount; } public void setBeraterCount(Integer beraterCount) { this.beraterCount = beraterCount; } @Column public Integer getMandantCount() { return mandantCount; } public void setMandantCount(Integer mandantCount) { this.mandantCount = mandantCount; } @Column public Integer getAssetCount() { return assetCount; } public void setAssetCount(Integer assetCount) { this.assetCount = assetCount; } @Column public Integer getSyncCount() { return syncCount; } public void setSyncCount(Integer syncCount) { this.syncCount = syncCount; } @Column @Type(type = "uuid-char") public UUID getContextId() { return contextId; } public void setContextId(UUID contextId) { this.contextId = contextId; } @Column @Type(type = "uuid-char") public UUID getParentContextId() { return parentContextId; } public void setParentContextId(UUID parentContextId) { this.parentContextId = parentContextId; } @Column @Type(type = "yes_no") public boolean isHidden() { return hidden; } public void setHidden(boolean hidden) { this.hidden = hidden; } } ==== Dynamic insert and update ==== Dynamic insert and update means hibernate will check and make a query with the changes only. This is handy for queries that grow too big (like multiple text columns). @DynamicInsert @DynamicUpdate ===== DAO Interface ===== Now we could use a DAO interface package be.mentoringsystems.merke.persistence; import be.mentoringsystems.merke.model.QueryParams; import be.mentoringsystems.merke.model.db.Login; import java.util.List; import java.util.UUID; /** * * @author anthonyarents */ public interface LoginDAO { void save(Login obj); Login getById(UUID id); Login getByUsername(String username); List getAll(QueryParams queryParams); Long getCount(QueryParams queryParams); } ===== DAO Implementation ===== ==== JPA CriteriaQuery via Hibernate ==== === Implementations === package de.jcpis.analysestats.persistence.hibernate; import de.jcpis.analysestats.helper.ConversionHelper; import de.jcpis.analysestats.model.QueryParams; import de.jcpis.analysestats.model.Sorting; import de.jcpis.analysestats.model.db.Context; import de.jcpis.analysestats.persistence.ContextDAO; import java.util.ArrayList; import java.util.List; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Order; import javax.persistence.criteria.Root; import org.apache.commons.lang3.StringUtils; import org.hibernate.Hibernate; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; /** * * @author anthonyarents */ @Repository public class ContextDAOImpl implements ContextDAO { @Autowired private SessionFactory sessionFactory; @Override public void save(final Context obj) { sessionFactory.getCurrentSession().saveOrUpdate(obj); } @Override public void delete(final Context obj) { sessionFactory.getCurrentSession().delete(obj); } @Override public Context getById(final Integer id) { return sessionFactory.getCurrentSession().get(Context.class, id); } private void initializeDependencies(final List list) { for (Context obj : list) { initializeDependencies(obj); } } private void initializeDependencies(final Context obj) { if (obj != null) { Hibernate.initialize(obj.getServer()); } } private void addFilters(final CriteriaBuilder builder, final Root root, final CriteriaQuery criteriaQuery, final QueryParams queryParams) { final String name = ConversionHelper.toString(queryParams.getFilterValue("name")); if (!StringUtils.isEmpty(name)) { criteriaQuery.where(builder.like(root.get("name"), name + "%")); } } private void addSorting(final CriteriaBuilder builder, final Root root, final CriteriaQuery criteriaQuery, final QueryParams queryParams) { if (queryParams.getSort().getSortings().isEmpty()) { criteriaQuery.orderBy(builder.asc(root.get("name"))); } else { final List orderList = new ArrayList(); for (Sorting s : queryParams.getSort().getSortings()) { if ("ASC".equals(s.getDirection())) { orderList.add(builder.asc(root.get(s.getProperty()))); } else { orderList.add(builder.desc(root.get(s.getProperty()))); } } criteriaQuery.orderBy(orderList); } } @Override public List getAll(final QueryParams queryParams) { final Session session = sessionFactory.getCurrentSession(); session.enableFilter("deletedObjectsFilter"); final CriteriaBuilder builder = session.getCriteriaBuilder(); final CriteriaQuery criteriaQuery = builder.createQuery(Context.class); final Root root = criteriaQuery.from(Context.class); criteriaQuery.select(root); addFilters(builder, root, criteriaQuery, queryParams); addSorting(builder, root, criteriaQuery, queryParams); final TypedQuery typedQuery = session.createQuery(criteriaQuery); typedQuery.setFirstResult(queryParams.getStart()); if (queryParams.isLimitted()) { typedQuery.setMaxResults(queryParams.getLimit()); } return typedQuery.getResultList(); } @Override public Long getCount(final QueryParams queryParams) { final Session session = sessionFactory.getCurrentSession(); session.enableFilter("deletedObjectsFilter"); final CriteriaBuilder builder = session.getCriteriaBuilder(); final CriteriaQuery criteriaQuery = builder.createQuery(Long.class); final Root root = criteriaQuery.from(Context.class); criteriaQuery.select(builder.count(root)); addFilters(builder, root, criteriaQuery, queryParams); final TypedQuery typedQuery = session.createQuery(criteriaQuery); return typedQuery.getSingleResult(); } } ==== *OLD* Hibernate Criteria API (legacy) ==== === 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()); } } === Hibernate DAO implementations === 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 result = (List) 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 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) 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 result = (List) 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 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 years = ConversionHelper.toIntegerList(queryParams.getFilterValue("years")); final List venueIds = ConversionHelper.toUUIDList(queryParams.getFilterValue("venueIds")); final List categories = ConversionHelper.toUUIDList(queryParams.getFilterValue("categoryIds")); final List 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 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 result = (List) 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 result = (List) getHibernateTemplate().findByCriteria(criteria); return result.get(0); } } Advanced hibernate criteria queries exist, If you need examples contact me :-). ==== Statistics / advanced queries with Hibernate (Legacy) ==== @SuppressWarnings("unchecked") public Map getStats(Context context) { final HibernateTemplate template = getHibernateTemplate(); final org.hibernate.classic.Session session = template.getSessionFactory().openSession(); final List list = session.createSQLQuery( "select " + "(select count(id) from Login where contextId=:contextId and active='Y') as beraterCount, " + "(select count(id) from Mandant where deleted='N' and beraterId in(select beraterId from Login where contextId=:contextId and active='Y')) as mandantCount, " + "(select count(id) from Asset where deleted='N' and analyseId in(select id from Analyse where mandantId in(select id from Mandant where deleted='N' and beraterId in(select beraterId from Login where contextId=:contextId and active='Y')))) as assetCount, " + "(select count(id) from RequestLog where logtype='mobile request' and beraterId in(select beraterId from Login where contextId=:contextId and active='Y')) as syncCount" ).setParameter("contextId", context.getId().toString()).list(); Object[] objs = list.get(0); Map result = new HashMap(); result.put("beraterCount", objs[0]); result.put("mandantCount", objs[1]); result.put("assetCount", objs[2]); result.put("syncCount", objs[3]); final List list2 = session.createSQLQuery("select YEAR(createdOn), MONTH(createdOn), COUNT(id) from Mandant where deleted='N' and beraterId in(select beraterId from Login where contextId=:contextId and active='Y') group by YEAR(createdOn) ASC, MONTH(createdOn) ASC").setParameter("contextId", context.getId().toString()).list(); List> mandantStats = new ArrayList>(); for(Object[] objs2 : list2) { Map mandantstat = new HashMap(); mandantstat.put("year", objs2[0]); mandantstat.put("month", objs2[1]); mandantstat.put("count", objs2[2]); mandantStats.add(mandantstat); } result.put("mandantStats", mandantStats); return result; } ===== Service Interface ===== Of note : extends UserDetailsService (allows for custom spring security login)\\ package be.mentoringsystems.merke.service; import be.mentoringsystems.merke.model.QueryParams; import be.mentoringsystems.merke.model.db.Login; import be.mentoringsystems.merke.model.dto.LoginDTO; import be.mentoringsystems.merke.model.dto.PagedListDTO; import java.util.List; import java.util.UUID; import org.springframework.security.core.userdetails.UserDetailsService; /** * * @author anthonyarents */ public interface LoginService extends UserDetailsService { void save(Login obj); Login save(LoginDTO dto); void delete(Login obj); void delete(UUID id); Login getById(UUID id); Login getByUsername(String login); List getAll(QueryParams queryParams); Long getCount(QueryParams queryParams); PagedListDTO getAllDTO(QueryParams queryParams); Login getCurrentLogin(); Login getSourceLogin(); void changePassword(String password); void changePassword(Login login, String password); Login getByUsernameAndPassword(String username, String password); } package be.mentoringsystems.merke.service; import be.mentoringsystems.merke.model.QueryParams; import be.mentoringsystems.merke.model.db.Vendor; import be.mentoringsystems.merke.model.dto.PagedListDTO; import be.mentoringsystems.merke.model.dto.VendorDTO; import java.util.List; import java.util.UUID; /** * * @author anthonyarents */ public interface VendorService { void save(Vendor obj); Vendor save(VendorDTO dto); void delete(Vendor obj); void delete(UUID id); Vendor getById(UUID id); Vendor getByLoginId(UUID loginId); List getAll(QueryParams queryParams); Long getCount(QueryParams queryParams); PagedListDTO getAllDTO(QueryParams queryParams); boolean addMunicipality(UUID vendorId, UUID municipalityId); boolean removeMunicipality(UUID vendorId, UUID municipalityId); } ===== Service Implementation ===== package be.mentoringsystems.merke.service.impl; import be.mentoringsystems.merke.helper.DTOHelper; import be.mentoringsystems.merke.model.QueryParams; import be.mentoringsystems.merke.model.db.Login; import be.mentoringsystems.merke.model.db.Vendor; import be.mentoringsystems.merke.model.dto.LoginDTO; import be.mentoringsystems.merke.model.dto.PagedListDTO; import be.mentoringsystems.merke.persistence.LoginDAO; import be.mentoringsystems.merke.service.ContextService; import be.mentoringsystems.merke.service.LoginService; import be.mentoringsystems.merke.service.VendorService; import java.util.ArrayList; import java.util.List; import java.util.UUID; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; /** * * @author anthonyarents */ @Service @Transactional(propagation = Propagation.SUPPORTS) public class LoginServiceImpl implements LoginService { @Autowired private transient LoginDAO loginDAO; @Autowired private transient ContextService contextService; @Autowired private transient VendorService vendorService; @Autowired private transient PasswordEncoder passwordEncoder; private static final Logger LOGGER = LogManager.getLogger(LoginServiceImpl.class); @Override @Transactional(propagation = Propagation.REQUIRED) public void save(final Login login) { loginDAO.save(login); } @Override @Transactional(propagation = Propagation.REQUIRED) public Login save(final LoginDTO dto) { final Login login = this.getCurrentLogin(); Login obj; boolean createVendor = false; if (dto.getId() == null) { obj = new Login(); createVendor = true; LOGGER.info("Creating a new Login"); } else { obj = getById(dto.getId()); LOGGER.info("Updating existing Login({})", dto.getId()); LOGGER.info("ContextId for this login:" + dto.getContextId()); if (login.getContextId() != null && !login.getContextId().equals(obj.getContextId()) && !login.isMSAdmin()) { obj = null; LOGGER.info("Access not allowed"); } } if (obj != null) { // save password if it exists String dtoPassword = dto.getPassword(); dto.setPassword(null); // copy non null DTOHelper.copyNonNull(dto, obj); // if password was not null : if (StringUtils.hasText(dtoPassword)) { obj.setPassword(encryptPassword(dtoPassword)); } if (dto.getContextId() != null) { obj.setContextId(dto.getContextId()); obj.setContext(contextService.getById(dto.getContextId())); } else { //Add context of current login if (login != null && login.getContextId() != null) { obj.setContextId(login.getContextId()); obj.setContext(contextService.getById(obj.getContextId())); } } LOGGER.info("Context before saving:" + obj.getContextId()); save(obj); LOGGER.info("Login({}) saved", obj.getId() + " context: " + obj.getContextId()); if (createVendor && obj.getGroup() == 2) { LOGGER.info("Creating a new Vendor"); final Vendor vendor = new Vendor(); vendor.setFirstname(obj.getFirstname()); vendor.setLastname(obj.getLastname()); vendor.setEmail(obj.getEmail()); vendor.setLoginId(obj.getId()); vendor.setFlag(0); vendor.setActive(true); vendorService.save(vendor); LOGGER.info("Vendor({}) saved", vendor.getId()); } else if (obj.getGroup() == 2) { LOGGER.info("Updating Vendor"); Vendor vendor = vendorService.getByLoginId(obj.getId()); vendor.setFirstname(obj.getFirstname()); vendor.setLastname(obj.getLastname()); vendor.setEmail(obj.getEmail()); vendorService.save(vendor); LOGGER.info("Vendor({}) saved", vendor.getId()); } } return obj; } @Override @Transactional(propagation = Propagation.REQUIRED) public void delete(final Login login) { login.setDeleted(true); save(login); LOGGER.info("Login({}) deleted", login.getId()); } @Override @Transactional(propagation = Propagation.REQUIRED) public void delete(final UUID id) { final Login login = getById(id); delete(login); } @Override @Transactional(readOnly = true) public Login getById(final UUID id) { return loginDAO.getById(id); } @Override @Transactional(readOnly = true) public Login getByUsername(final String username) { return loginDAO.getByUsername(username); } @Override @Transactional(readOnly = true) public List getAll(final QueryParams queryParams) { return loginDAO.getAll(queryParams); } @Override @Transactional(readOnly = true) public Long getCount(final QueryParams queryParams) { return loginDAO.getCount(queryParams); } @Override @Transactional(readOnly = true) public PagedListDTO getAllDTO(final QueryParams queryParams) { final Login login = this.getCurrentLogin(); if (login.getContextId() != null && !login.isMSAdmin()) { queryParams.addFilter("contextId", login.getContextId()); } final PagedListDTO pagedListDTO = new PagedListDTO(); pagedListDTO.setData(getAll(queryParams)); pagedListDTO.setTotal(getCount(queryParams)); return pagedListDTO; } @Override @Transactional(propagation = Propagation.REQUIRED) public void changePassword(final String password) { final Login login = getCurrentLogin(); changePassword(login, password); } @Override @Transactional(propagation = Propagation.REQUIRED) public void changePassword(final Login login, final String password) { login.setPassword(encryptPassword(password)); save(login); LOGGER.info("Login({}) password changed", login.getId()); } public String encryptPassword(final String password) { return passwordEncoder.encode(password); } @Override @Transactional(readOnly = true) public UserDetails loadUserByUsername(final String loginName) throws UsernameNotFoundException { final Login login = getByUsername(loginName); if (login == null) { throw new UsernameNotFoundException(loginName); } else { login.setAuthorities(getAuthorities(login)); return login; } } public List getAuthorities(final Login login) { final List authorities = new ArrayList<>(); if (login.isMSAdmin()) { authorities.add(new SimpleGrantedAuthority("ROLE_MSADMIN")); authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN")); } if (login.isAdmin()) { authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN")); } if (login.isFireman()) { authorities.add(new SimpleGrantedAuthority("ROLE_FIREMAN")); } authorities.add(new SimpleGrantedAuthority("ROLE_USER")); return authorities; } @Override public Login getCurrentLogin() { if (SecurityContextHolder.getContext() != null && SecurityContextHolder.getContext().getAuthentication() != null) { Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (principal instanceof Login) { return (Login) principal; } } return null; } @Override public Login getSourceLogin() { final Login current = getCurrentLogin(); Login original = null; for (GrantedAuthority auth : current.getAuthorities()) { if (auth instanceof SwitchUserGrantedAuthority) { Authentication authSource = ((SwitchUserGrantedAuthority) auth).getSource(); if (authSource instanceof Login) { original = (Login) authSource; } } } return original; } @Override @Transactional(readOnly = true) public Login getByUsernameAndPassword(final String username, final String password) { final Login login = getByUsername(username); if (login == null) { return null; } else { if (passwordEncoder.matches(password, login.getPassword())) { return login; } else { return null; } } } } package be.mentoringsystems.merke.service.impl; import be.mentoringsystems.merke.helper.DTOHelper; import be.mentoringsystems.merke.model.QueryParams; import be.mentoringsystems.merke.model.db.Context; import be.mentoringsystems.merke.model.db.Login; import be.mentoringsystems.merke.model.db.Municipality; import be.mentoringsystems.merke.model.db.Vendor; import be.mentoringsystems.merke.model.db.Vendorstall; import be.mentoringsystems.merke.model.dto.PagedListDTO; import be.mentoringsystems.merke.model.dto.VendorDTO; import be.mentoringsystems.merke.persistence.VendorDAO; import be.mentoringsystems.merke.service.ContextService; import be.mentoringsystems.merke.service.LoginService; import be.mentoringsystems.merke.service.MunicipalityService; import be.mentoringsystems.merke.service.VendorService; import be.mentoringsystems.merke.service.VendorstallService; import java.util.ArrayList; import java.util.List; import java.util.UUID; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; /** * * @author anthonyarents */ @Service @Transactional(propagation = Propagation.SUPPORTS) public class VendorServiceImpl implements VendorService { @Autowired private transient VendorDAO vendorDAO; @Autowired private transient LoginService loginService; @Autowired private transient VendorstallService vendorstallService; @Autowired private transient MunicipalityService municipalityService; @Autowired private transient ContextService contextService; private static final Logger LOGGER = LogManager.getLogger(VendorServiceImpl.class); @Override @Transactional(propagation = Propagation.REQUIRED) public void save(final Vendor obj) { vendorDAO.save(obj); } @Override @Transactional(propagation = Propagation.REQUIRED) public Vendor save(final VendorDTO dto) { Vendor obj; if (dto.getId() == null) { obj = new Vendor(); LOGGER.info("Creating a new Vendor"); } else { if (dto.getMunicipalityIds() == null) { obj = getById(dto.getId()); } else { obj = vendorDAO.getByIdWithMunicipalities(dto.getId()); } LOGGER.info("Updating existing Vendor({})", dto.getId()); } if (obj != null) { DTOHelper.copyNonNull(dto, obj); if (dto.getMunicipalityIds() != null) { List lm = new ArrayList<>(); List municipalitiesWithContext = new ArrayList<>(); List municipalitiesWithoutContext = new ArrayList<>(); List subscribedContexts = obj.getContexts(); for (UUID mid : dto.getMunicipalityIds()) { lm.add(municipalityService.getById(mid)); } obj.getMunicipalities().retainAll(lm); lm.removeAll(obj.getMunicipalities()); if (!lm.isEmpty()) { lm.forEach((newlyAddedMunicipality) -> { if (newlyAddedMunicipality.getContextId() != null) { municipalitiesWithContext.add(newlyAddedMunicipality); } else { municipalitiesWithoutContext.add(newlyAddedMunicipality); } }); obj.getMunicipalities().addAll(lm); } if (!municipalitiesWithContext.isEmpty()) { municipalitiesWithContext.forEach((municipality) -> { Context cityInMerke = contextService.getById(municipality.getContextId()); if (!subscribedContexts.contains(cityInMerke)) { //TODO : ask user for confirmation first! //If user says no, add to seperate list so we prevent asking the same thing twice? subscribedContexts.add(cityInMerke); } }); } if (!municipalitiesWithoutContext.isEmpty()) { municipalitiesWithoutContext.forEach((municipality) -> { //TODO : send message to user which municipalities they want to subscribe to, then save those in a seperate list? }); } } //obj.setActive(true); save(obj); LOGGER.info("Vendor({}) saved", obj.getId()); LOGGER.info("Updating Login"); final Login login = loginService.getById(obj.getLoginId()); login.setEmail(obj.getEmail()); login.setFirstname(obj.getFirstname()); login.setLastname(obj.getLastname()); loginService.save(login); LOGGER.info("Login({}) saved", login.getId()); } return obj; } @Override @Transactional(propagation = Propagation.REQUIRED) public void delete(final Vendor obj) { final QueryParams params = new QueryParams(); params.addFilter("vendorId", obj.getId()); List stalls = vendorstallService.getAll(params); for (Vendorstall stall : stalls) { vendorstallService.delete(stall); } obj.setDeleted(true); save(obj); LOGGER.info("Vendor({}) deleted", obj.getId()); loginService.delete(obj.getLoginId()); } @Override @Transactional(propagation = Propagation.REQUIRED) public void delete(final UUID id) { final Vendor obj = getById(id); delete(obj); } @Override @Transactional(readOnly = true) public Vendor getById(final UUID id) { return vendorDAO.getById(id); } @Override @Transactional(readOnly = true) public Vendor getByLoginId(final UUID loginId) { QueryParams queryParams = new QueryParams(); queryParams.addFilter("loginId", loginId); List vendors = getAll(queryParams); if (vendors.isEmpty()) { return null; } else { return vendors.get(0); } } @Override @Transactional(propagation = Propagation.REQUIRED) public boolean addMunicipality(final UUID vendorId, final UUID municipalityId) { final Vendor vendor = getById(vendorId); final Municipality municipality = municipalityService.getById(municipalityId); if (!vendor.getMunicipalities().contains(municipality)) { vendor.getMunicipalities().add(municipality); save(vendor); } return true; } @Override @Transactional(propagation = Propagation.REQUIRED) public boolean removeMunicipality(final UUID vendorId, final UUID municipalityId) { final Vendor vendor = getById(vendorId); final Municipality municipality = municipalityService.getById(municipalityId); if (vendor.getMunicipalities().contains(municipality)) { vendor.getMunicipalities().remove(municipality); save(vendor); } return true; } @Override @Transactional(readOnly = true) public List getAll(final QueryParams queryParams) { return vendorDAO.getAll(queryParams); } @Override @Transactional(readOnly = true) public Long getCount(final QueryParams queryParams) { return vendorDAO.getCount(queryParams); } @Override @Transactional(readOnly = true) public PagedListDTO getAllDTO(final QueryParams queryParams) { queryParams.addFilter("contextId", loginService.getCurrentLogin().getContextId()); final PagedListDTO result = new PagedListDTO(); result.setData(getAll(queryParams)); result.setTotal(getCount(queryParams)); return result; } } ===== View ===== Jsonview values, notice we'll be using public as a default. package be.mentoringsystems.merke.model.dto; /** * * @author anthonyarents */ public class View { public interface Public { }; public interface Vendor extends Public { }; public interface Login extends Public { }; public interface Venue extends Public { }; public interface Certificate extends Public { }; public interface VenueEvent extends Public { }; public interface VendorstallPlacement extends Public { }; } ===== Data Transfer Objects (DTO) ===== We're using DTO objects to have more control of incoming data. package be.mentoringsystems.merke.model.dto; import java.util.UUID; /** * * @author anthonyarents */ public class LoginDTO { private UUID id; private String firstname; private String lastname; private String email; private String password; private Integer group; private String username; private Boolean active; private String language; private Boolean fairvendor; private Boolean marketvendor; private UUID contextId; private UUID pictureId; public UUID getPictureId() { return pictureId; } public void setPictureId(final UUID pictureId) { this.pictureId = pictureId; } public UUID getContextId() { return contextId; } public void setContextId(final UUID contextId) { this.contextId = contextId; } public Boolean getFairvendor() { return fairvendor; } public void setFairvendor(final Boolean fairvendor) { this.fairvendor = fairvendor; } public Boolean getMarketvendor() { return marketvendor; } public void setMarketvendor(final Boolean marketvendor) { this.marketvendor = marketvendor; } public String getLanguage() { return language; } public void setLanguage(final String language) { this.language = language; } public Boolean getActive() { return active; } public void setActive(final Boolean active) { this.active = active; } public UUID getId() { return id; } public void setId(final UUID id) { this.id = id; } public String getEmail() { return email; } public void setEmail(final String email) { this.email = email; } public String getFirstname() { return firstname; } public void setFirstname(final String firstname) { this.firstname = firstname; } public Integer getGroup() { return group; } public void setGroup(final Integer group) { this.group = group; } public String getLastname() { return lastname; } public void setLastname(final String lastname) { this.lastname = lastname; } public String getUsername() { return username; } public void setUsername(final String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(final String password) { this.password = password; } } package be.mentoringsystems.merke.model.dto; import be.mentoringsystems.merke.model.db.Context; import com.fasterxml.jackson.annotation.JsonFormat; import java.time.LocalDate; import java.util.List; import java.util.UUID; import org.springframework.format.annotation.DateTimeFormat; /** * * @author anthonyarents */ public class VendorDTO { private UUID id; private UUID parentid; private UUID loginId; private String title; private String title2; private String accounttype; private String firstname; private String lastname; private String idnrNationalRegister; private String street; private String number; private String mailboxNumber; private String postalcode; private String city; private String companyName; private String companyNumber; private String bankaccount; private String telephonenumber; private String email; private String mobilenumber; private String mobilenumber2; private LocalDate birthdate; private String permitNumber; private String status; private String change; private Integer flag; private String flagnote; private List contexts; private List municipalityIds; private UUID contextId; public String getAccounttype() { return accounttype; } public void setAccounttype(final String accounttype) { this.accounttype = accounttype; } public UUID getContextId() { return contextId; } public void setContextId(final UUID contextId) { this.contextId = contextId; } public List getMunicipalityIds() { return municipalityIds; } public void setMunicipalityIds(final List municipalityIds) { this.municipalityIds = municipalityIds; } public List getContexts() { return contexts; } public void setContexts(final List contexts) { this.contexts = contexts; } public Integer getFlag() { return flag; } public void setFlag(final Integer flag) { this.flag = flag; } public String getFlagnote() { return flagnote; } public void setFlagnote(final String flagnote) { this.flagnote = flagnote; } public String getTitle2() { return title2; } public void setTitle2(final String title2) { this.title2 = title2; } public String getChange() { return change; } public void setChange(final String change) { this.change = change; } public String getStatus() { return status; } public void setStatus(final String status) { this.status = status; } public String getPermitNumber() { return permitNumber; } public void setPermitNumber(final String permitNumber) { this.permitNumber = permitNumber; } public UUID getId() { return id; } public void setId(final UUID id) { this.id = id; } public UUID getLoginId() { return loginId; } public void setLoginId(final UUID loginId) { this.loginId = loginId; } public UUID getParentid() { return parentid; } public void setParentid(final UUID parentid) { this.parentid = parentid; } public String getTitle() { return title; } public void setTitle(final String title) { this.title = title; } public String getFirstname() { return firstname; } public void setFirstname(final String firstname) { this.firstname = firstname; } public String getLastname() { return lastname; } public void setLastname(final String lastname) { this.lastname = lastname; } public String getIdnrNationalRegister() { return idnrNationalRegister; } public void setIdnrNationalRegister(final String idnrNationalRegister) { this.idnrNationalRegister = idnrNationalRegister; } public String getStreet() { return street; } public void setStreet(final String street) { this.street = street; } public String getNumber() { return number; } public void setNumber(final String number) { this.number = number; } public String getMailboxNumber() { return mailboxNumber; } public void setMailboxNumber(final String mailboxNumber) { this.mailboxNumber = mailboxNumber; } public String getPostalcode() { return postalcode; } public void setPostalcode(final String postalcode) { this.postalcode = postalcode; } public String getCity() { return city; } public void setCity(final String city) { this.city = city; } public String getCompanyName() { return companyName; } public void setCompanyName(final String companyName) { this.companyName = companyName; } public String getCompanyNumber() { return companyNumber; } public void setCompanyNumber(final String companyNumber) { this.companyNumber = companyNumber; } public String getBankaccount() { return bankaccount; } public void setBankaccount(final String bankaccount) { this.bankaccount = bankaccount; } public String getTelephonenumber() { return telephonenumber; } public void setTelephonenumber(final String telephonenumber) { this.telephonenumber = telephonenumber; } public String getEmail() { return email; } public void setEmail(final String email) { this.email = email; } @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) public LocalDate getBirthdate() { return birthdate; } public void setBirthdate(final LocalDate birthdate) { this.birthdate = birthdate; } public String getMobilenumber() { return mobilenumber; } public void setMobilenumber(final String mobilenumber) { this.mobilenumber = mobilenumber; } public String getMobilenumber2() { return mobilenumber2; } public void setMobilenumber2(final String mobilenumber2) { this.mobilenumber2 = mobilenumber2; } } It's also useful for outgoing data : package be.mentoringsystems.merke.model.dto; import com.fasterxml.jackson.annotation.JsonInclude; import java.util.ArrayList; import java.util.List; /** * * @author anthonyarents */ @JsonInclude(JsonInclude.Include.NON_NULL) public class ApiDTO { private Boolean success = true; private Object data; private List messages = new ArrayList(); public Object getData() { return data; } public void setData(final Object data) { this.data = data; } public List getMessages() { return messages; } public void setMessages(final List messages) { this.messages = messages; } public void addMessage(final String message) { this.messages.add(message); } public Boolean getSuccess() { return success; } public void setSuccess(final Boolean success) { this.success = success; } public ApiDTO(final Object data) { this.data = data; if (data == null) { this.success = false; } } public ApiDTO() { } public static ApiDTO error() { final ApiDTO apiDto = new ApiDTO(); apiDto.setSuccess(Boolean.FALSE); return apiDto; } public static ApiDTO error(final List messages) { final ApiDTO apiDto = new ApiDTO(); apiDto.setSuccess(Boolean.FALSE); apiDto.setMessages(messages); return apiDto; } } and paging : package be.mentoringsystems.merke.model.dto; import com.fasterxml.jackson.annotation.JsonInclude; import java.util.List; /** * * @author anthonyarents */ @JsonInclude(JsonInclude.Include.NON_NULL) public class PagedListDTO { private List data; private Long total; private Boolean success = true; public List getData() { return data; } public void setData(final List data) { this.data = data; } public Boolean getSuccess() { return success; } public void setSuccess(final Boolean success) { this.success = success; } public Long getTotal() { return total; } public void setTotal(final Long total) { this.total = total; } public void setTotal(final int total) { this.setTotal(Long.valueOf(total)); } } ===== *OLD* Presentation (Ajax, MultiActionController) ===== use requestmapping to map based on action parameter package de.jcpis.analyse.presentation; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.view.json.MappingJackson2JsonView; /** * * @author anthonyarents */ @Controller @RequestMapping("/bankverbindung.json") public class BankverbindungController { @RequestMapping(params = "action=validateBankverbindung") public ModelAndView validateBankverbindung(final WebRequest hsr) { } } ===== Presentation (Restcontroller) ===== Of note : * Do not use DAO classes here! * @PreAuthorize, adds a little security (role based here, still blind to ACL) * @JsonView, \\ I recommend adding it default : "@JsonView(View.Public.class)" to every method which returns a ''model.db'' or packages a ''model.db''.\\ The benefit for you : you'll be able to choose the view used here : ''View.Public'' = all except for the fields marked with ''@JsonView'' extending ''View.Public''. package be.mentoringsystems.merke.presentation.rest; import be.mentoringsystems.merke.model.QueryParams; import be.mentoringsystems.merke.model.db.Login; import be.mentoringsystems.merke.model.dto.ApiDTO; import be.mentoringsystems.merke.model.dto.LoginDTO; import be.mentoringsystems.merke.model.dto.PagedListDTO; import be.mentoringsystems.merke.model.dto.View; import be.mentoringsystems.merke.security.JWSHelper; import be.mentoringsystems.merke.service.LoginService; import com.fasterxml.jackson.annotation.JsonView; import java.util.UUID; import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; /** * * @author anthonyarents */ @RestController @RequestMapping("/logins") public class LoginController { @Autowired private LoginService loginService; @RequestMapping(value = "/authenticate", method = RequestMethod.POST) public ApiDTO authenticate(@RequestParam(required = false) final String username, @RequestParam(required = false) final String password, final HttpServletRequest hsr) { final ApiDTO dto = ApiDTO.error(); String session = JWSHelper.getJwsSubject(hsr); Login login = null; if (session == null) { login = loginService.getCurrentLogin(); } else { login = loginService.getByUsername(session); } if (login == null) { login = loginService.getByUsernameAndPassword(username, password); } if (login != null) { String jws = JWSHelper.generateJwsWithSubject(login.getUsername()); login.setJwt(jws); dto.setData(login); dto.setSuccess(Boolean.TRUE); } return dto; } @PreAuthorize("hasRole('ROLE_ADMIN')") @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) @ResponseStatus(HttpStatus.OK) public ApiDTO deleteById(@PathVariable final UUID id) { loginService.delete(id); return new ApiDTO(); } @PreAuthorize("hasRole('ROLE_ADMIN')") @JsonView(View.Login.class) @RequestMapping(value = "/{id}", method = RequestMethod.PUT) @ResponseStatus(HttpStatus.OK) public ApiDTO updateById(@PathVariable final UUID id, @RequestBody @Validated final LoginDTO dto, final BindingResult bindingResult) { if (bindingResult.hasErrors()) { return ApiDTO.error(); } dto.setId(id); final Login result = loginService.save(dto); return new ApiDTO(result); } @PreAuthorize("@methodSecurity.hasLoginAccess(#id)") @JsonView(View.Login.class) @RequestMapping(value = "/gdprCheck", method = RequestMethod.POST) @ResponseStatus(HttpStatus.OK) public ApiDTO gdprCheck(@RequestParam final UUID id) { Login login = loginService.getCurrentLogin(); if (login != null && login.getId().equals(id)) { login.setGdprCheck(true); loginService.save(login); return new ApiDTO(); } else { return ApiDTO.error(); } } @PreAuthorize("@methodSecurity.hasLoginAccess(#id)") @JsonView(View.Login.class) @RequestMapping(value = "/updatepicture", method = RequestMethod.POST) @ResponseStatus(HttpStatus.OK) public ApiDTO updatepicture(@RequestParam final UUID id, @RequestParam final UUID pictureId) { Login login = loginService.getCurrentLogin(); if (login.getId().equals(id)) { login.setPictureId(pictureId); loginService.save(login); } else { LoginDTO dto = new LoginDTO(); dto.setId(id); dto.setPictureId(pictureId); login = loginService.save(dto); } return new ApiDTO(login); } @JsonView(View.Login.class) @PreAuthorize("hasRole('ROLE_ADMIN')") @RequestMapping(value = "", method = RequestMethod.POST) @ResponseStatus(HttpStatus.CREATED) public ApiDTO createNew(@RequestBody @Validated final LoginDTO dto, final BindingResult bindingResult) { if (bindingResult.hasErrors()) { return ApiDTO.error(); } final Login result = loginService.save(dto); return new ApiDTO(result); } @JsonView(View.Login.class) @PreAuthorize("hasRole('ROLE_ADMIN')") @RequestMapping(value = "/{id}", method = RequestMethod.GET) public ApiDTO getById(@PathVariable final UUID id) { final Login result = loginService.getById(id); return new ApiDTO(result); } @JsonView(View.Login.class) @PreAuthorize("hasRole('ROLE_ADMIN')") @RequestMapping(value = "", method = RequestMethod.GET) public PagedListDTO getAll(final QueryParams queryParams) { return loginService.getAllDTO(queryParams); } } 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 java.util.ArrayList; import java.util.List; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.http.HttpStatus; import org.springframework.security.access.AccessDeniedException; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.WebRequest; /** * * @author anthonyarents */ @ControllerAdvice(annotations = RestController.class) public class Advice { private static final Logger LOGGER = LogManager.getLogger(Advice.class); @InitBinder("queryParams") public void initBinderForQueryParams(final WebDataBinder binder) { binder.registerCustomEditor(FilterMap.class, new FilterMapEditor()); binder.registerCustomEditor(SortingMap.class, new SortingMapEditor()); } @ExceptionHandler(value = AccessDeniedException.class) @ResponseStatus(HttpStatus.FORBIDDEN) @ResponseBody public ApiDTO accessDeniedException(final AccessDeniedException exception, final WebRequest request) { LOGGER.error(exception.getMessage()); final List messageList = new ArrayList(); messageList.add(exception.getMessage()); return ApiDTO.error(messageList); //return ApiDTO.error(Throwables.toStringList(exception)); } @ExceptionHandler(value = Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ResponseBody public ApiDTO exception(final Exception exception, final WebRequest request) { final List messageList = new ArrayList(); if ("org.apache.catalina.connector.ClientAbortException".equals(exception.getClass().getCanonicalName())) { LOGGER.debug(exception.getMessage()); //LOGGER.info("ignored client abort"); return null; } else { LOGGER.error(exception.getMessage(), exception); messageList.add(exception.getMessage()); } return ApiDTO.error(messageList); } } ==== Multiple Dateformat support ==== When you need to support reading 2 dateformats package be.mentoringsystems.paypermail.presentation; import java.text.DateFormat; import java.text.FieldPosition; import java.text.ParsePosition; import java.util.ArrayList; import java.util.Date; import java.util.List; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** * * @author anthonyarents */ public class MultipleDateFormat extends DateFormat { private List formats = new ArrayList(); private static final Logger LOGGER = LogManager.getLogger(MultipleDateFormat.class); public void addFormat(final DateFormat format) { formats.add(format); } @Override public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public Date parse(String source, ParsePosition pos) { Date result = null; for (DateFormat format : formats) { try { result = format.parse(source, pos); } catch (Exception ex) { LOGGER.error(ex.getMessage(), ex); } if (result != null) { break; } } return result; } } ===== Presentation (Conversion) ===== package be.mentoringsystems.merke.presentation.conversion; import be.mentoringsystems.merke.model.Filter; import be.mentoringsystems.merke.model.FilterMap; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; import java.beans.PropertyEditorSupport; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** * * @author anthonyarents */ public class FilterMapEditor extends PropertyEditorSupport { private static final Logger LOGGER = LogManager.getLogger(FilterMapEditor.class); @Override public void setAsText(final String text) throws IllegalArgumentException { ObjectMapper mapper = new ObjectMapper(); final FilterMap filterMap = new FilterMap(); try { final List 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(final String text) throws IllegalArgumentException { ObjectMapper mapper = new ObjectMapper(); final SortingMap sortingMap = new SortingMap(); try { final List sorters = mapper.readValue(text, TypeFactory.defaultInstance().constructCollectionType(ArrayList.class, Sorting.class)); sortingMap.setSortings(sorters); } catch (IOException ex) { LOGGER.error(ex.getMessage(), ex); } setValue(sortingMap); } } ===== Security Annotations ===== Several guides are available for this, I recommend to just burn them all. ==== Annotations provided by Spring Security ==== @PreAuthorize("hasRole('ROLE_ADMIN')") [[https://docs.spring.io/spring-security/site/docs/current/reference/html/el-access.html]] ==== Method Security (aka do it yourself) ==== @methodSecurity.hasLoginAccess(#id) package be.mentoringsystems.merke.security; import be.mentoringsystems.merke.model.QueryParams; import be.mentoringsystems.merke.model.db.Attendance; import be.mentoringsystems.merke.model.db.Certificate; import be.mentoringsystems.merke.model.db.Context; import be.mentoringsystems.merke.model.db.File; import be.mentoringsystems.merke.model.db.Login; import be.mentoringsystems.merke.model.db.Remark; import be.mentoringsystems.merke.model.db.Vendor; import be.mentoringsystems.merke.model.db.VendorRequest; import be.mentoringsystems.merke.model.db.Vendorstall; import be.mentoringsystems.merke.model.db.VendorstallPlacement; import be.mentoringsystems.merke.model.dto.AttendanceDTO; import be.mentoringsystems.merke.model.dto.CertificateDTO; import be.mentoringsystems.merke.model.dto.FileDTO; import be.mentoringsystems.merke.model.dto.LoginDTO; import be.mentoringsystems.merke.model.dto.RemarkDTO; import be.mentoringsystems.merke.model.dto.VendorDTO; import be.mentoringsystems.merke.model.dto.VendorRequestDTO; import be.mentoringsystems.merke.model.dto.VendorstallDTO; import be.mentoringsystems.merke.model.dto.VendorstallPlacementDTO; import be.mentoringsystems.merke.service.AttendanceService; import be.mentoringsystems.merke.service.CertificateService; import be.mentoringsystems.merke.service.ContextService; import be.mentoringsystems.merke.service.FileService; import be.mentoringsystems.merke.service.LoginService; import be.mentoringsystems.merke.service.RemarkService; import be.mentoringsystems.merke.service.VendorRequestService; import be.mentoringsystems.merke.service.VendorService; import be.mentoringsystems.merke.service.VendorstallPlacementService; import be.mentoringsystems.merke.service.VendorstallService; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.AccessDeniedException; import org.springframework.stereotype.Component; /** * * @author anthonyarents */ @Component("methodSecurity") public class MethodSecurity { @Autowired private transient LoginService loginService; @Autowired private transient VendorService vendorService; @Autowired private transient VendorstallService vendorstallService; @Autowired private transient VendorstallPlacementService vendorstallPlacementService; @Autowired private transient AttendanceService attendanceService; @Autowired private transient CertificateService certificateService; @Autowired private transient FileService fileService; @Autowired private transient RemarkService remarkService; @Autowired private transient VendorRequestService vendorRequestService; @Autowired private transient ContextService contextService; // QueryParams public boolean vendorLimited(final QueryParams queryParams) throws AccessDeniedException { final Login login = loginService.getCurrentLogin(); if (!login.isAdmin() && !login.isMSAdmin() && !login.isFireman()) { final Vendor vendor = vendorService.getByLoginId(login.getId()); if (!vendor.getId().toString().equals(queryParams.getFilterValue("vendorId"))) { return false; } } return true; } // Attendance public boolean hasAttendanceAccess(final AttendanceDTO dto, final UUID id) { dto.setId(id); return hasAttendanceAccess(dto); } public boolean hasAttendanceAccess(final AttendanceDTO dto) { boolean hasAccess = true; if (dto.getVendorId() != null) { hasAccess = hasVendorAccess(dto.getVendorId()); } if (hasAccess && dto.getVendorstallId() != null) { hasAccess = hasVendorstallAccess(dto.getVendorstallId()); } if (hasAccess && dto.getId() != null) { return hasAttendanceAccess(dto.getId()); } return hasAccess; } public boolean hasAttendanceAccess(final UUID id) { boolean hasAccess = true; Attendance obj = attendanceService.getById(id); if (obj == null) { hasAccess = false; } else { if (hasAccess && obj.getVendorId() != null) { hasAccess = hasVendorAccess(obj.getVendorId()); } if (hasAccess && obj.getVendorstallId() != null) { hasAccess = hasVendorstallAccess(obj.getVendorstallId()); } } return hasAccess; } // Certificate public boolean hasCertificateAccess(final CertificateDTO dto, final UUID id) { dto.setId(id); return hasCertificateAccess(dto); } public boolean hasCertificateAccess(final CertificateDTO dto) { boolean hasAccess = true; if (dto.getVendorId() != null) { hasAccess = hasVendorAccess(dto.getVendorId()); } if (hasAccess && dto.getVendorstallId() != null) { hasAccess = hasVendorstallAccess(dto.getVendorstallId()); } if (hasAccess && dto.getId() != null) { return hasCertificateAccess(dto.getId()); } return hasAccess; } public boolean hasCertificateAccess(final UUID id) { boolean hasAccess = true; Certificate obj = certificateService.getById(id); if (obj == null) { hasAccess = false; } else { if (hasAccess && obj.getVendorId() != null) { hasAccess = hasVendorAccess(obj.getVendorId()); } if (hasAccess && obj.getVendorstallId() != null) { hasAccess = hasVendorstallAccess(obj.getVendorstallId()); } } return hasAccess; } // Certificate public boolean hasFileAccess(final FileDTO dto, final UUID id) { dto.setId(id); return hasFileAccess(dto); } public boolean hasFileAccess(final FileDTO dto) { boolean hasAccess = true; if (dto.getVendorId() != null) { hasAccess = hasVendorAccess(dto.getVendorId()); } if (hasAccess && dto.getVendorstallId() != null) { hasAccess = hasVendorstallAccess(dto.getVendorstallId()); } if (hasAccess && dto.getId() != null) { return hasFileAccess(dto.getId()); } return hasAccess; } public boolean hasFileAccess(final UUID id) { boolean hasAccess = true; File obj = fileService.getById(id); if (obj == null) { hasAccess = false; } else { if (hasAccess && obj.getVendorId() != null) { hasAccess = hasVendorAccess(obj.getVendorId()); } if (hasAccess && obj.getVendorstallId() != null) { hasAccess = hasVendorstallAccess(obj.getVendorstallId()); } } return hasAccess; } // Remark public boolean hasRemarkAccess(final RemarkDTO dto, final UUID id) { dto.setId(id); return hasRemarkAccess(dto); } public boolean hasRemarkAccess(final RemarkDTO dto) { boolean hasAccess = true; if (dto.getVendorId() != null) { hasAccess = hasVendorAccess(dto.getVendorId()); } if (hasAccess && dto.getVendorstallId() != null) { hasAccess = hasVendorstallAccess(dto.getVendorstallId()); } if (hasAccess && dto.getId() != null) { return hasRemarkAccess(dto.getId()); } return hasAccess; } public boolean hasRemarkAccess(final UUID id) { boolean hasAccess = true; Remark obj = remarkService.getById(id); if (obj == null) { hasAccess = false; } else { if (hasAccess && obj.getVendorId() != null) { hasAccess = hasVendorAccess(obj.getVendorId()); } if (hasAccess && obj.getVendorstallId() != null) { hasAccess = hasVendorstallAccess(obj.getVendorstallId()); } } return hasAccess; } // Vendor public boolean hasVendorAccess(final VendorDTO dto, final UUID vendorId) { dto.setId(vendorId); return hasVendorAccess(dto); } public boolean hasVendorAccess(final VendorDTO dto) { return hasVendorAccess(dto.getId()); } public boolean hasVendorAccess(final UUID vendorId) { Login login = loginService.getCurrentLogin(); return hasVendorAccess(login, vendorId); } // VendorRequest public boolean hasVendorRequestAccess(final VendorRequestDTO dto, final UUID id) { dto.setId(id); return hasVendorRequestAccess(dto); } public boolean hasVendorRequestAccess(final VendorRequestDTO dto) { boolean hasAccess = true; if (dto.getVendorId() != null) { hasAccess = hasVendorAccess(dto.getVendorId()); } if (hasAccess && dto.getVendorstallId() != null) { hasAccess = hasVendorstallAccess(dto.getVendorstallId()); } if (hasAccess && dto.getId() != null) { return hasVendorRequestAccess(dto.getId()); } return hasAccess; } public boolean hasVendorRequestAccess(final UUID id) { boolean hasAccess = true; VendorRequest obj = vendorRequestService.getById(id); if (obj == null) { hasAccess = false; } else { if (hasAccess && obj.getVendorId() != null) { hasAccess = hasVendorAccess(obj.getVendorId()); } if (hasAccess && obj.getVendorstallId() != null) { hasAccess = hasVendorstallAccess(obj.getVendorstallId()); } } return hasAccess; } // Vendorstall public boolean hasVendorstallAccess(final VendorstallDTO dto, final UUID vendorstallId) { dto.setId(vendorstallId); return hasVendorstallAccess(dto); } public boolean hasVendorstallAccess(final VendorstallDTO dto) { return hasVendorstallAccess(dto.getId()); } public boolean hasVendorstallAccess(final UUID vendorstallId) { Login login = loginService.getCurrentLogin(); return hasVendorstallAccess(login, vendorstallId); } public boolean hasVendorstallPlacementAccess(final VendorstallPlacementDTO dto, final UUID vendorstallPlacementId) { dto.setId(vendorstallPlacementId); return hasVendorstallPlacementAccess(dto); } public boolean hasVendorstallPlacementAccess(final VendorstallPlacementDTO dto) { return hasVendorstallPlacementAccess(dto.getId()); } public boolean hasVendorstallPlacementAccess(final UUID vendorstallPlacementId) { Login login = loginService.getCurrentLogin(); return hasVendorstallPlacementAccess(login, vendorstallPlacementId); } public boolean hasLoginAccess(final LoginDTO dto, final UUID loginId) { dto.setId(loginId); return hasLoginAccess(dto); } public boolean hasLoginAccess(final LoginDTO dto) { return hasLoginAccess(dto.getId()); } public boolean hasLoginAccess(final UUID loginId) { Login login = loginService.getCurrentLogin(); return hasLoginAccess(login, loginId); } public boolean hasLoginAccess(final Login login, final UUID loginId) { if (login == null) { return false; } else if (login.isMSAdmin()) { return true; } else { if (loginId == null) { return false; } else { if (loginId.equals(login.getId())) { return true; } else { if (login.getContextId() == null) { return false; } else { final Context context = contextService.getById(login.getContextId()); final Vendor v = vendorService.getByLoginId(loginId); if (v != null && v.getContexts().contains(context)) { return login.isAdmin() || login.isFireman() || login.isPolice(); } else { return false; } } } } } } // HelperMethods private boolean hasVendorAccess(final Login login, final UUID vendorId) { if (login == null) { return false; } else if (login.isMSAdmin()) { return true; } else { if (vendorId == null) { return false; } else { Vendor v = vendorService.getById(vendorId); if (v == null) { return false; } else { if (v.getLoginId().equals(login.getId())) { return true; } else { if (login.getContextId() == null) { return false; } else { final Context context = contextService.getById(login.getContextId()); if (v.getContexts().contains(context)) { return login.isAdmin() || login.isFireman() || login.isPolice(); } else { return false; } } } } } } } private boolean hasVendorAdminAccess(final Login login, final Vendor vendor) { if (vendor == null) { return login.isMSAdmin(); } else { //TODO extra checks for vendor return true; } } public boolean hasVendorAdminAccess(final UUID vendorId) { final Login login = loginService.getCurrentLogin(); return hasVendorAdminAccess(login, vendorId); } private boolean hasVendorAdminAccess(final Login login, final UUID vendorId) { if (login == null) { return false; } else if (login.isMSAdmin()) { return true; } else if (login.isAdmin()) { if (vendorId == null) { return true; // creating a new obj } else { final Vendor v = vendorService.getById(vendorId); return hasVendorAdminAccess(login, v); } } else { return false; } } private boolean hasVendorstallAccess(final Login login, final UUID vendorstallId) { if (login == null) { return false; } else if (login.isMSAdmin()) { return true; } else { if (vendorstallId == null) { return true; } else { final Vendorstall vendorstall = vendorstallService.getById(vendorstallId); if (vendorstall == null) { return false; } else { return hasVendorAccess(login, vendorstall.getVendorId()); } } } } private boolean hasVendorstallPlacementAccess(final Login login, final UUID vendorstallPlacementId) { if (login == null) { return false; } else if (login.isMSAdmin()) { return true; } else { if (vendorstallPlacementId == null) { return true; } else { final VendorstallPlacement vendorstallPlacement = vendorstallPlacementService.getById(vendorstallPlacementId); if (vendorstallPlacement == null) { return false; } else { return hasVendorstallAccess(login, vendorstallPlacement.getVendorstallId()); } } } } } ===== (Optional) CSRF - Cross site Request Forgery ===== Usually, CSRF is turned off (it's a lot of extra stuff to do), CSRF protection requires you to secure POST, PUT, DELETE (not GET) ==== Configuration change ==== Comment out a line in our SecurityConfiguration : @Override protected void configure(final HttpSecurity http) throws Exception { //http.csrf().disable(); ... } ==== Controller ==== package be.mentoringsystems.abstracts.presentation; import org.springframework.security.web.csrf.CsrfToken; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class CsrfController { @RequestMapping("/csrf") @ResponseBody public CsrfToken csrf(CsrfToken token) { return token; } } ==== Example javascript usage (jquery) ==== // Need to use a get request to get the active csrf token (renews token before usage) function useToken(callback) { $.ajax('csrf', { method: 'get' }).done(function (json) { callback.call(this, json); }); } // csrf param example useToken(function(csrf) { var data = { agree: $('#privacyagree').is(":checked") }; data[csrf.parameterName] = csrf.token; $.ajax('privacyCheck', { method: 'post', data: data }).done(function (json) { if(json.success) { $('#privacyModal').modal('hide'); } else { $('#message').html("It's not possible to continue without accepting the privacy policy."); $('#myModal').modal(); } }); }); // csrf header example useToken(function(csrf) { var data = { agree: $('#privacyagree').is(":checked") }; var headers = {}; headers['Content-Type'] = 'application/json'; headers[csrf.headerName] = csrf.token; $.ajax('privacyCheck', { method: 'post', data: data, headers: headers, dataType: 'json' }).done(function (json) { if(json.success) { $('#privacyModal').modal('hide'); } else { $('#message').html("It's not possible to continue without accepting the privacy policy."); $('#myModal').modal(); } }); }); ==== Logout ==== As mentionned earlier, Logout needs to be POST + secured by the csrf token the following javascript is used \\ $('#logoutbtn').click(function () { useToken(function(csrf){ var el = ''; $('#logout').append(el); $('#logout').submit(); }); }); \\ This also requires a hidden form :\\ ===== JSON web tokens ===== For adding JWT support Add to pom: 3.4.1 com.auth0 java-jwt ${auth0.jwt} Helper classes package be.mentoringsystems.merke.security; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.DecodedJWT; import java.nio.charset.StandardCharsets; import javax.servlet.http.HttpServletRequest; /** * * @author anthonyarents */ public final class JWSHelper { private static final String SECRET = "F6deJ;xShXD+juV,%dtt}3/z:pZ6-H"; public static final String TOKEN_PREFIX = "Bearer "; public static final String HEADER_NAME = "Authorization"; private JWSHelper() { // } public static String getJwsSubject(final HttpServletRequest hsr) { String subject = null; String authorization = hsr.getHeader(HEADER_NAME); if (authorization == null) { authorization = hsr.getParameter(HEADER_NAME); } if (authorization != null && authorization.startsWith(TOKEN_PREFIX)) { DecodedJWT decoded = JWT.require(Algorithm.HMAC512(SECRET.getBytes(StandardCharsets.UTF_8))).build().verify(authorization.replace(TOKEN_PREFIX, "")); subject = decoded.getSubject(); } return subject; } public static String generateJwsWithSubject(final String subject) { return JWT.create().withSubject(subject).sign(Algorithm.HMAC512(SECRET.getBytes(StandardCharsets.UTF_8))); } } package be.mentoringsystems.merke.security; import be.mentoringsystems.merke.model.db.Login; import be.mentoringsystems.merke.service.LoginService; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; /** * * @author anthonyarents */ @Component public class JWTAuthenticationFilter implements Filter { @Autowired private LoginService loginService; private static final Logger LOGGER = LogManager.getLogger(JWTAuthenticationFilter.class); @Override public void doFilter(final ServletRequest sr, final ServletResponse sr1, final FilterChain fc) throws IOException, ServletException { if (sr instanceof HttpServletRequest) { final HttpServletRequest hsr = (HttpServletRequest) sr; final HttpServletResponse hsr1 = (HttpServletResponse) sr1; String username = JWSHelper.getJwsSubject(hsr); Login login = loginService.getCurrentLogin(); if (username == null) { LOGGER.debug("found no jwt token"); if (login != null) { LOGGER.debug("user is logged in, making a new jwt token"); String token = JWSHelper.generateJwsWithSubject(login.getEmail()); hsr1.addHeader(JWSHelper.HEADER_NAME, JWSHelper.TOKEN_PREFIX + token); } } else { LOGGER.debug("found jwt token"); if (login == null) { UserDetails details = loginService.getByUsername(username); if (details != null) { details = loginService.loadUserByUsername(username); LOGGER.debug("user is not logged in, logging in as {0}", details.getUsername()); UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(details, null, details.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(auth); } } //hsr1.addHeader(JWSHelper.HEADER_NAME, JWSHelper.TOKEN_PREFIX + token); } } fc.doFilter(sr, sr1); } @Override public void init(final FilterConfig fc) throws ServletException { } @Override public void destroy() { } } Add method to loginController: @RequestMapping(value = "/authenticate", method = RequestMethod.POST) public ApiDTO authenticate(@RequestParam(required = false) final String username, @RequestParam(required = false) final String password, final HttpServletRequest hsr) { final ApiDTO dto = ApiDTO.error(); String session = JWSHelper.getJwsSubject(hsr); Login login = null; if (session != null) { login = loginService.getByUsername(session); } if (login == null) { login = loginService.getByUsernameAndPassword(username, password); } if (login != null) { String jws = JWSHelper.generateJwsWithSubject(login.getUsername()); login.setJwt(jws); dto.setData(login); dto.setSuccess(Boolean.TRUE); } return dto; } Add transient jwt parameter to Login: private String jwt; @Transient public String getJwt() { return jwt; } public void setJwt(final String jwt) { this.jwt = jwt; } Add method to LoginServiceImpl if it doesn't exist: @Override @Transactional(readOnly = true) public Login getByUsernameAndPassword(final String username, final String password) { final Login login = getByUsername(username); if (login == null) { return null; } else { if (passwordEncoder.matches(password, login.getPassword())) { return login; } else { return null; } } } Add filter to securityConfiguration package be.trustandchain.mobi.config; import be.trustandchain.mobi.security.JWTAuthenticationFilter; import be.trustandchain.mobi.service.LoginService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; /** * * @author anthonyarents */ @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired private LoginService loginService; @Autowired private JWTAuthenticationFilter jwtAuthenticationFilter; @Override public void configure(final WebSecurity web) throws Exception { web.ignoring().antMatchers("/static/**", "/app/**"); } @Override protected void configure(final HttpSecurity http) throws Exception { // temporary http.csrf().disable(); http.headers().frameOptions().sameOrigin(); http.userDetailsService(loginService); http .logout().logoutUrl("/logout").permitAll() // logout page, permitall is necessary .and() .authorizeRequests() // request matching with ant .antMatchers("/rest/**").permitAll() .anyRequest().permitAll(); // we're not checking if ppl are logged in, we do rolechecks on individual methods http.addFilterAfter(jwtAuthenticationFilter, BasicAuthenticationFilter.class); } @Bean public PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } @Override protected void configure(final AuthenticationManagerBuilder auth) throws Exception { PasswordEncoder passwordEncoder = passwordEncoder(); auth.userDetailsService(loginService).passwordEncoder(passwordEncoder); /*LOGGER.info("admin"); LOGGER.info(passwordEncoder.encode("admin")); LOGGER.info("p55VrKYE"); LOGGER.info(passwordEncoder.encode("p55VrKYE")); LOGGER.info("dD6fd7bT"); LOGGER.info(passwordEncoder.encode("dD6fd7bT")); LOGGER.info("s7cvJJC3"); LOGGER.info(passwordEncoder.encode("s7cvJJC3")); LOGGER.info("GwcEA9hP"); LOGGER.info(passwordEncoder.encode("GwcEA9hP")); LOGGER.info("RFYuV9ac"); LOGGER.info(passwordEncoder.encode("RFYuV9ac")); LOGGER.info("ZBjMnY5E"); LOGGER.info(passwordEncoder.encode("ZBjMnY5E")); LOGGER.info("reTkBWJFtUQnEV3R"); LOGGER.info(passwordEncoder.encode("reTkBWJFtUQnEV3R"));*/ } } ===== Server-side validation ===== At this time, I've only added a check to disallow "dangerous" html on fields which will display the html as is (instead of htmlEncoded like the rest of the output) extra requirements on the project : * el-api (v2.2 is used for tomcat6 & tomcat7, v3.0.0 is used with java8 & tomcat8) * hibernate-validator (v5.4.1.Final in case of tomcat6 & 7, use latest in case of java8 & tomcat8) * jsoup (use latest) ==== DTO ==== === imports === import org.hibernate.validator.constraints.SafeHtml; === Getter === @SafeHtml public String getDescription() { return description; } ==== Presentation ==== === Imports === import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; === Changes to methods updateById & createNew === @JsonView(View.Public.class) @PreAuthorize("hasRole('ROLE_ADMIN')") @RequestMapping(value = "/{id}", method = RequestMethod.PUT) @ResponseStatus(HttpStatus.OK) public ApiDTO updateById(@PathVariable final UUID id, @RequestBody @Validated final NotificationDTO dto, final BindingResult bindingResult) { if (bindingResult.hasErrors()) { return ApiDTO.error(); } dto.setId(id); final Notification result = notificationService.save(dto); return new ApiDTO(result); } @JsonView(View.Public.class) @PreAuthorize("hasRole('ROLE_ADMIN')") @RequestMapping(value = "", method = RequestMethod.POST) @ResponseStatus(HttpStatus.CREATED) public ApiDTO createNew(@RequestBody @Validated final NotificationDTO dto, final BindingResult bindingResult) { if (bindingResult.hasErrors()) { return ApiDTO.error(); } final Notification result = notificationService.save(dto); return new ApiDTO(result); } ===== Locale handling ===== ==== Additional MvcConfiguration ==== @Bean public LocaleResolver localeResolver(){ CustomLocaleResolver resolver = new CustomLocaleResolver(); resolver.setDefaultLocale(new Locale("nl")); resolver.setCookieName("myLocaleCookie"); resolver.setCookieMaxAge(4800); return resolver; } @Bean public LocaleChangeInterceptor localeChangeInterceptor(){ LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor(); interceptor.setParamName("mylocale"); return interceptor; } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(localeChangeInterceptor()); } ==== Locale resolver ==== package be.mentoringsystems.merke.presentation.locale; import be.mentoringsystems.merke.model.db.Login; import be.mentoringsystems.merke.service.LoginService; import java.util.Locale; import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.servlet.i18n.CookieLocaleResolver; import static org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE_REQUEST_ATTRIBUTE_NAME; import static org.springframework.web.servlet.i18n.CookieLocaleResolver.TIME_ZONE_REQUEST_ATTRIBUTE_NAME; /** * * @author anthonyarents */ public class CustomLocaleResolver extends CookieLocaleResolver { @Autowired private LoginService loginService; @Override public Locale resolveLocale(final HttpServletRequest request) { final Login login = loginService.getCurrentLogin(); if (login != null) { final Locale locale = new Locale(login.getLanguage()); request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME, locale); request.setAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME, determineDefaultTimeZone(request)); return locale; } else { return super.resolveLocale(request); } } } ===== Pdf handling ===== For filling Pdf fields : There's a PdfFieldInspector (project is in our projects SVN) java -jar PdfFieldInspector-1.0-SNAPSHOT-jar-with-dependencies.jar /Users/anthonyarents/NetBeansProjects/JCPIS_ANALYSE_BUILD_SUITE/src/main/gevas/WEB-INF/report/pdf/gevas/Maklervollmacht\ AcontrA.pdf ===== Excel handling ===== ==== ExportController ==== 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 objectMap = new HashMap(); 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 objectMap = new HashMap(); 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 map, Workbook wrkbk, HttpServletRequest hsr, HttpServletResponse hsr1) throws Exception { @SuppressWarnings("unchecked") final List data = (List) 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 map, Workbook wrkbk, HttpServletRequest hsr, HttpServletResponse hsr1) throws Exception { @SuppressWarnings("unchecked") final List data = (List) 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 categories = new ArrayList(); 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 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 columnHeaders = new ArrayList(); 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 columnHeaders, final List 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 columnHeaders, final UUID fileId) { Map result = new HashMap(); final List values = new ArrayList(); 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 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"%> BoardPortal

Aanmelden

===== App ===== Add ''index.jsp'' to ''/WEB-INF/jsp'' \\ this should be the index.html of your extjs application (slighly modified like :) BoardPortal ====== 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 7 ====== ===== A new App ===== ==== Setup ==== - Download and Install Sencha Cmd 6 - Download and unzip the Ext JS 6 SDK (remember the path) ==== To start a new EXTJS project ==== - Open your terminal or console window and issue these commands *inside* the static folder : sencha -sdk /path/to/ext6 generate app cd sencha app watch This will generate a skeleton app, ''sencha app watch'' will check for any changes made to css or js. ==== To take over an existing EXTJS project ==== * Open your terminal or console window and issue these commands *inside* the app folder : sencha app install --frameworks ~/Downloads/ExtJS/ sencha app watch This will allow you to work with the ext project, ''sencha app watch'' will check for any changes made to css or js. ==== To update an existing EXTJS project ==== * Open your terminal or console window and issue these commands *inside* the app folder : sencha app upgrade ~/Downloads/ExtJS/ext-6.6.0/ sencha app watch ==== Locale package for modern (<6.6.0) ==== download {{:java:locale.zip|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 ''/classic/src'', stylechanges in ''/classic/sass''\\ Mobile/Tablet code usually goes into ''/modern/src'', stylechanges in ''/modern/sass'' ==== Notes ==== * You can only use "classic" extjs classes in the classic folder, & "modern" (touch) classes in the modern folder. * Binding doesn't always work like it's supposed to, but it makes the code really neat and you should try it out. * Model relations don't always work like they're supposed to, try to avoid them * if a ui config doesn't work, it's likely that the name was already taken ===== Adjusting your build ==== **static/BoardPortal/app.json :** find the mentioning of **BUILDCHANGE** in the comments to differentiate between a production build & a development build ===== build for testing ===== * change app.json to use index.html (as mentionned in BUILDCHANGE comments) * go to static/BoardPortal with terminal/cmd * use "sencha app build classic" * use "sencha app watch" to deal with all javascript changes ===== build for production ===== * change app.json to use ../index.html (as mentionned in BUILDCHANGE comments) * go to static/BoardPortal with terminal/cmd * use "sencha app build classic" ===== Server preparations ===== [[server:ubuntu:ubuntu-installation-new|Server Setup Ubuntu 16]]