diff --git a/gh-2225/.externalToolBuilders/org.springframework.ide.eclipse.core.springbuilder.launch b/gh-2225/.externalToolBuilders/org.springframework.ide.eclipse.core.springbuilder.launch
new file mode 100644
index 0000000..bc08e10
--- /dev/null
+++ b/gh-2225/.externalToolBuilders/org.springframework.ide.eclipse.core.springbuilder.launch
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/gh-2225/.gitignore b/gh-2225/.gitignore
new file mode 100644
index 0000000..be96b94
--- /dev/null
+++ b/gh-2225/.gitignore
@@ -0,0 +1,57 @@
+.DS_Store
+.mimosa
+node_modules
+
+# Target Directory #
+####################
+/target
+
+# Compiled source #
+###################
+*.com
+*.class
+*.dll
+*.exe
+*.o
+*.so
+
+# Packages #
+############
+# it's better to unpack these files and commit the raw source
+# git has its own built in compression methods
+*.7z
+*.dmg
+*.gz
+*.iso
+*.jar
+*.rar
+*.tar
+*.zip
+
+# Logs #
+######################
+*.log
+
+# OS generated files #
+######################
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+/nbproject/*
+/.gradle
+/release.properties
+/build
+
+
+# Eclipse Files #
+#################
+.jstdscope
+.settings/**
+.clover/**
+.project
+.classpath
+/.clover/
diff --git a/gh-2225/.springBeans b/gh-2225/.springBeans
new file mode 100644
index 0000000..b7ebe3e
--- /dev/null
+++ b/gh-2225/.springBeans
@@ -0,0 +1,16 @@
+
+
+ 1
+
+
+
+
+
+
+ java:com.edlogics.ElrcApplication
+
+
+
+
+
+
diff --git a/gh-2225/pom.xml b/gh-2225/pom.xml
new file mode 100644
index 0000000..7d7e9fe
--- /dev/null
+++ b/gh-2225/pom.xml
@@ -0,0 +1,356 @@
+
+ 4.0.0
+ com.edlogics
+ JodaBootBug
+ war
+ 1-SNAPSHOT
+ TestProject
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 1.2.0.RELEASE
+
+
+
+ 1.7
+ UTF-8
+ UTF-8
+ 9.3-1100-jdbc41
+ 5.0.2.Final
+ 2.4.0.RELEASE
+ 2.0.3.RELEASE
+
+ 3.0
+
+
+
+
+ tomcat-embed-core
+ org.apache.tomcat.embed
+ provided
+
+
+ tomcat-embed-jasper
+ org.apache.tomcat.embed
+ provided
+
+
+ tomcat-embed-logging-juli
+ org.apache.tomcat.embed
+ provided
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-websocket
+
+
+ tomcat-embed-core
+ org.apache.tomcat.embed
+
+
+ tomcat-embed-logging-juli
+ org.apache.tomcat.embed
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ tomcat-embed-core
+ org.apache.tomcat.embed
+
+
+ tomcat-embed-logging-juli
+ org.apache.tomcat.embed
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-mobile
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
+
+ org.thymeleaf.extras
+ thymeleaf-extras-springsecurity3
+ compile
+
+
+ com.github.mxab.thymeleaf.extras
+ thymeleaf-extras-data-attribute
+
+
+ org.springframework
+ spring-messaging
+
+
+ javax.mail
+ mail
+ 1.4.7
+
+
+ org.springframework
+ spring-context-support
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-joda
+
+
+
+
+ org.projectreactor
+ reactor-tcp
+ 1.0.0.RELEASE
+
+
+ org.postgresql
+ postgresql
+ ${postgresql.version}
+
+
+ org.springframework.hateoas
+ spring-hateoas
+
+
+ org.springframework.plugin
+ spring-plugin-core
+ 0.8.0.RELEASE
+
+
+ org.springsource.loaded
+ springloaded
+ 1.1.5.RELEASE
+ provided
+
+
+ org.springframework.webflow
+ spring-webflow
+ ${spring-webflow.version}
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+ org.springframework.security.oauth
+ spring-security-oauth2
+ ${spring-security-oauth2.version}
+
+
+ commons-collections
+ commons-collections
+
+
+ commons-beanutils
+ commons-beanutils-core
+ 1.8.3
+
+
+ org.yaml
+ snakeyaml
+
+
+ org.apache.commons
+ commons-lang3
+ 3.3.2
+
+
+ com.googlecode.lambdaj
+ lambdaj
+ 2.4
+
+
+ org.hamcrest
+ hamcrest-all
+ 1.3
+ compile
+
+
+ uk.co.modular-it
+ hamcrest-date
+ 0.9.5
+
+
+ commons-io
+ commons-io
+ 2.4
+
+
+ commons-fileupload
+ commons-fileupload
+ 1.3.1
+
+
+ commons-validator
+ commons-validator
+ 1.4.0
+
+
+ com.univocity
+ univocity-parsers
+ 1.1.0
+
+
+ org.hibernate
+ hibernate-validator
+
+
+ org.hibernate
+ hibernate-core
+ ${hibernate.version}
+
+
+ org.hibernate
+ hibernate-entitymanager
+ ${hibernate.version}
+
+
+ org.hibernate
+ hibernate-envers
+ ${hibernate.version}
+
+
+ org.apache.httpcomponents
+ httpclient
+
+
+ org.apache.httpcomponents
+ httpmime
+
+
+ org.thymeleaf.extras
+ thymeleaf-extras-conditionalcomments
+ 2.1.1.RELEASE
+
+
+
+ com.mangofactory
+ swagger-springmvc
+ 0.8.2
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ nl.jqno.equalsverifier
+ equalsverifier
+ 1.4.1
+ test
+
+
+ com.jayway.jsonpath
+ json-path
+ test
+
+
+ org.mockito
+ mockito-core
+ 1.10.17
+ test
+
+
+
+ org.hsqldb
+ hsqldb
+
+
+ org.flywaydb
+ flyway-maven-plugin
+ ${flyway.version}
+
+
+ org.slf4j
+ slf4j-nop
+
+
+ org.slf4j
+ slf4j-jdk14
+
+
+
+
+ org.flywaydb.flyway-test-extensions
+ flyway-spring-test
+ ${flyway.version}
+
+
+ org.slf4j
+ slf4j-simple
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.18
+
+
+ org.apache.maven.surefire
+ surefire-junit47
+ 2.18
+
+
+
+ -Xms384m -Xmx512m -XX:PermSize=128m -XX:MaxPermSize=256m -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ ${java.version}
+ ${java.version}
+
+
+
+ org.codehaus.mojo
+ versions-maven-plugin
+
+
+ maven-scm-plugin
+
+ ${project.artifactId}-${project.version}
+
+
+
+
+
+ src/main/resources
+ true
+
+
+
+
+
\ No newline at end of file
diff --git a/gh-2225/src/main/java/com/edlogics/ElrcApplication.java b/gh-2225/src/main/java/com/edlogics/ElrcApplication.java
new file mode 100644
index 0000000..edfb84d
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/ElrcApplication.java
@@ -0,0 +1,290 @@
+/*
+ *
+ * Copyright EdLogics, LLC. All Rights Reserved.
+ *
+ * This software is the proprietary information of EdLogics, LLC.
+ * Use is subject to license terms.
+ *
+ */
+package com.edlogics;
+
+import static org.springframework.util.StringUtils.commaDelimitedListToStringArray;
+import static org.springframework.util.StringUtils.trimAllWhitespace;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.StringUtils;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.MutableDateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
+import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
+import org.springframework.boot.context.embedded.MimeMappings;
+import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.context.web.SpringBootServletInitializer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.context.annotation.Primary;
+import org.springframework.context.annotation.Scope;
+import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
+import org.springframework.hateoas.config.EnableEntityLinks;
+import org.springframework.hateoas.config.EnableHypermediaSupport;
+import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+import org.springframework.web.context.WebApplicationContext;
+
+import com.fasterxml.jackson.databind.Module;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.joda.JodaModule;
+
+/**
+ * This is the main class for Spring Boot to do the application startup and configuration
+ *
+ * @author Christopher Savory
+ *
+ */
+@Configuration
+@EnableAutoConfiguration
+@EnableConfigurationProperties
+@EnableTransactionManagement
+@EnableAspectJAutoProxy
+@EnableJpaAuditing
+@EnableEntityLinks
+@EnableAsync
+@EnableHypermediaSupport(type = HypermediaType.HAL)
+@ComponentScan
+public class ElrcApplication extends SpringBootServletInitializer implements EmbeddedServletContainerCustomizer {
+
+ protected Logger logger = LoggerFactory.getLogger( getClass() );
+
+ /**
+ * Holds Spring Boot configuration properties
+ */
+ protected Properties props = new Properties();
+
+ @Value("${app.session-timeout}")
+ private String sessionTimeout;
+
+ @Value("${app.timezone}")
+ String elrcTimezone;
+
+ public ElrcApplication() {}
+
+ /**
+ * Overrides the default configuration for the JSON Serialization on Spring REST
+ * Turns off the default date formatting
+ *
+ * @return the customized ObjectMapper
+ */
+ @Bean
+ @Primary
+ public ObjectMapper objectMapper()
+ {
+ ObjectMapper om = new ObjectMapper();
+ om.configure( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false );
+ return om;
+ }
+
+ /**
+ * Joda Module as a Bean. Spring will register this module with all ObjectMapper instances
+ *
+ * @return
+ */
+ @Bean
+ public Module jodaModule() {
+ return new JodaModule();
+ }
+
+ /**
+ * @return the Scheduler used
+ */
+ @Bean
+ public ThreadPoolTaskScheduler scheduler() {
+ ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
+ scheduler.setPoolSize( 10 );
+ return scheduler;
+ }
+
+ /**
+ * Bean to hold the system maintenance date time. Defaults to a far future date time.
+ *
+ * @return
+ */
+ @Bean
+ public MutableDateTime maintenanceDateTime() {
+ MutableDateTime farFuture = MutableDateTime.now( DateTimeZone.forID( elrcTimezone ) );
+ // Set to year 9999 to make easy to check for far future value
+ // There is no easy way to do a max Date with Joda time so this is the workaround
+ farFuture.setYear( 9999 );
+ return farFuture;
+ }
+
+ /**
+ * If the application is currently in maintenance as specified by the maintenance date time being set.
+ *
+ * @return Is the current date time > maintenance date time?
+ */
+ @Bean
+ @Scope(WebApplicationContext.SCOPE_REQUEST)
+ public boolean inMaintenance() {
+ DateTime currentDateTime = DateTime.now( DateTimeZone.forID( elrcTimezone ) );
+ return ( currentDateTime.getMillis() - maintenanceDateTime().getMillis() ) > 0;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.boot.web.SpringBootServletInitializer#configure(org.springframework.boot.builder.SpringApplicationBuilder)
+ */
+ @Override
+ protected SpringApplicationBuilder configure( SpringApplicationBuilder application ) {
+ application.sources( ElrcApplication.class );
+
+ // Set active profiles.
+ List profiles = new ArrayList();
+
+ String hostName = getHostname();
+
+ if ( StringUtils.isNotBlank( System.getProperty( "spring.profiles.active" ) ) ) {
+ for ( String profile : commaDelimitedListToStringArray( trimAllWhitespace( System.getProperty( "spring.profiles.active" ) ) ) ) {
+ profiles.add( profile );
+ }
+ } else if ( "christohersmbp2".equals( hostName ) || "Christophers-MacBook-Pro-2.local".equals( hostName ) ) {
+ profiles.add( "local" );
+ profiles.add( "chris" );
+ } else if ( "erics-mbp.home".equals( hostName ) || "Erics-MacBook-Pro.local".equals( hostName ) || "Erics-MBP.home".equals( hostName ) ) {
+ profiles.add( "local" );
+ profiles.add( "eric" );
+ } else if ( "James".equals( hostName ) ) {
+ profiles.add( "local" );
+ profiles.add( "james" );
+ } else if ( "pc-main".equals( hostName ) ) {
+ profiles.add( "local" );
+ profiles.add( "james" );
+ } else if ( "kevins-iMac.home".equals( hostName ) ) {
+ profiles.add( "local" );
+ profiles.add( "kevin" );
+ } else if ( "Jackson-THINK".equals( hostName ) ) {
+ profiles.add( "local" );
+ profiles.add( "david" );
+ } else if ( "endor".equals( hostName ) || "endor.local".equals( hostName ) ) {
+ profiles.add( "local" );
+ profiles.add( "jeff" );
+ } else if ( "ALIEN".equalsIgnoreCase( hostName ) ) {
+ profiles.add( "local" );
+ profiles.add( "alien" );
+ } else if ( "BhaskarReddy".equals( hostName ) ) {
+ profiles.add( "local" );
+ profiles.add( "bhaskar" );
+ } else if ( "Jasons-MacBook-Pro.local".equalsIgnoreCase( hostName ) || "alien2".equalsIgnoreCase( hostName ) ) {
+ profiles.add( "local" );
+ profiles.add( "alien2" );
+ } else if ( "edlogics".equalsIgnoreCase( hostName ) ) {
+ profiles.add( "local" );
+ profiles.add( "edlogics-vm" );
+ } else if ( "Banashley-pc".equalsIgnoreCase( hostName ) ) {
+ profiles.add( "local" );
+ profiles.add( "ben" );
+ }
+
+ logger.info( "Spring Boot configuration: profiles = " + profiles );
+ application.profiles( profiles.toArray( new String[profiles.size()] ) );
+
+ // Set additional properties. Note: this API does not exist in 0.5.0.M5
+ // or earlier.
+ logger.info( "Spring Boot configuration: properties = " + props );
+ application.properties( props ); // New API
+
+ return application;
+ }
+
+ @Override
+ public void customize( ConfigurableEmbeddedServletContainer container ) {
+ MimeMappings mappings = new MimeMappings( MimeMappings.DEFAULT );
+ mappings.add( "ico", "image/vnd.microsoft.icon" );
+ mappings.add( "woff", "application/font-woff" );
+ mappings.add( "ttf", "application/octet-stream" );
+ container.setMimeMappings( mappings );
+
+ /*
+ * Establishes a time limit / time out for each users session.
+ *
+ * Setting the key in the application.yml server.session-timeout : 15 does not accurately
+ * set the time limit / time out for each users session so it was removed.
+ *
+ * There seems to be an inconsistency with setting the aforementioned value and then the value
+ * of session.getMaxInterval(). When setting the server.session-timeout : 15 the value of session.getMaxInterval()
+ * is not set to the same value. From our observation the value of session.getMaxInterval() seems to be the
+ * result of a calculation derived from server.session-timeout : 15 but we are not able to track down where that occurs.
+ *
+ * There is a Jira issue open with Oracle to resolve setting a session time out similar to how you used to be able
+ * to set in prior to Servlet Spec 3.0. You can find the issue here: https://java.net/jira/browse/SERVLET_SPEC-70
+ */
+ if ( StringUtils.isNotEmpty( sessionTimeout ) && container instanceof TomcatEmbeddedServletContainerFactory ) {
+ TomcatEmbeddedServletContainerFactory factory = (TomcatEmbeddedServletContainerFactory) container;
+ factory.setSessionTimeout( Integer.valueOf( sessionTimeout ), TimeUnit.MINUTES );
+ }
+ }
+
+ /**
+ * This is called when running locally as Spring Boot, but not from Tomcat
+ *
+ * @param args
+ */
+ public static void main( String[] args ) {
+
+ System.setProperty( "jsse.enableSNIExtension", "false" );
+
+ // Create an instance and invoke run(); Allows the constructor to perform
+ // Initialization regardless of whether we are running as an application
+ // or in a container.
+ new ElrcApplication().runAsJavaApplication( args );
+ }
+
+ /**
+ * Run the application using Spring Boot. SpringApplication.run
tells Spring Boot to use this class as the initialiser for the whole
+ * application (via the class annotations above). This method is only used
+ * when running as a Java application.
+ *
+ * @param args
+ * Any command line arguments.
+ */
+ protected void runAsJavaApplication( String[] args ) {
+ SpringApplicationBuilder application = new SpringApplicationBuilder();
+ configure( application );
+
+ //Start the Server
+ application.run( args );
+ }
+
+ /**
+ * get the host's hostname
+ * return hostname
+ */
+ protected String getHostname() {
+ String hostname = null;
+ try {
+ hostname = InetAddress.getLocalHost().getHostName();
+ } catch ( UnknownHostException e ) {
+ e.printStackTrace();
+ }
+
+ return hostname;
+ }
+}
\ No newline at end of file
diff --git a/gh-2225/src/main/java/com/edlogics/common/Constants.java b/gh-2225/src/main/java/com/edlogics/common/Constants.java
new file mode 100644
index 0000000..39a683f
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/common/Constants.java
@@ -0,0 +1,19 @@
+/*
+ *
+ * Copyright EdLogics, LLC. All Rights Reserved.
+ *
+ * This software is the proprietary information of EdLogics, LLC.
+ * Use is subject to license terms.
+ *
+ */
+package com.edlogics.common;
+
+import java.util.regex.Pattern;
+
+public class Constants {
+
+ public static final String UTC_DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
+
+ public static Pattern nonDigitsPattern = Pattern.compile( "[^0-9]+" );
+
+}
diff --git a/gh-2225/src/main/java/com/edlogics/common/domain/IdKeepingSequenceGenerator.java b/gh-2225/src/main/java/com/edlogics/common/domain/IdKeepingSequenceGenerator.java
new file mode 100644
index 0000000..37bfa9e
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/common/domain/IdKeepingSequenceGenerator.java
@@ -0,0 +1,37 @@
+/*
+ *
+ * Copyright EdLogics, LLC. All Rights Reserved.
+ *
+ * This software is the proprietary information of EdLogics, LLC.
+ * Use is subject to license terms.
+ *
+ */
+package com.edlogics.common.domain;
+
+import java.io.Serializable;
+
+import org.hibernate.engine.spi.SessionImplementor;
+import org.hibernate.id.SequenceGenerator;
+import org.hibernate.id.SequenceHiLoGenerator;
+import org.springframework.data.domain.Persistable;
+
+/**
+ * A subclass of {@link SequenceGenerator} that will keep an Id if one is set.
+ * This condition will be rare, e.g. Batch Imports
+ *
+ * @author Christopher Savory
+ *
+ */
+public class IdKeepingSequenceGenerator extends SequenceHiLoGenerator {
+
+ @Override
+ public Serializable generate( SessionImplementor session, Object object ) {
+ if ( object instanceof Persistable ) {
+ Persistable extends Serializable> persistable = (Persistable>) object;
+ if ( persistable.getId() != null ) {
+ return persistable.getId();
+ }
+ }
+ return super.generate( session, object );
+ }
+}
\ No newline at end of file
diff --git a/gh-2225/src/main/java/com/edlogics/common/http/filters/SimpleCorsFilter.java b/gh-2225/src/main/java/com/edlogics/common/http/filters/SimpleCorsFilter.java
new file mode 100644
index 0000000..a559eb2
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/common/http/filters/SimpleCorsFilter.java
@@ -0,0 +1,70 @@
+/*
+ *
+ * Copyright EdLogics, LLC. All Rights Reserved.
+ *
+ * This software is the proprietary information of EdLogics, LLC.
+ * Use is subject to license terms.
+ *
+ */
+package com.edlogics.common.http.filters;
+
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+/**
+ * @author edlogics
+ *
+ */
+@Component
+public class SimpleCorsFilter extends OncePerRequestFilter {
+
+ private static final String TTF = "ttf";
+ private static final String TTC = "ttc";
+ private static final String OTF = "otf";
+ private static final String EOT = "eot";
+ private static final String WOFF = "woff";
+ private static final String FONT_CSS = "font.css";
+ private static final String CSS = "css";
+
+ public SimpleCorsFilter() {
+ super();
+ }
+
+ @Override
+ protected void doFilterInternal(
+ HttpServletRequest request,
+ HttpServletResponse response,
+ FilterChain filterChain ) throws ServletException, IOException {
+ if ( shouldAddCorsHeaders( request ) ) {
+ response.addHeader( "Access-Control-Allow-Origin", "*" );
+ response.addHeader( "Access-Control-Allow-Methods", "GET" );
+ response.addHeader( "Access-Control-Max-Age", "3600" );
+ response.addHeader( "Access-Control-Allow-Credentials", "true" );
+ response.addHeader( "Access-Control-Allow-Headers", "Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers" );
+ response.addHeader( "Access-Control-Expose-Headers", "Access-Control-Allow-Origin,Access-Control-Allow-Credentials" );
+ }
+ filterChain.doFilter( request, response );
+ }
+
+ protected boolean shouldAddCorsHeaders( HttpServletRequest request ) throws ServletException, IOException {
+ String requestedUri = request.getRequestURI();
+ if ( StringUtils.endsWithIgnoreCase( requestedUri, TTF ) ||
+ StringUtils.endsWithIgnoreCase( requestedUri, TTC ) ||
+ StringUtils.endsWithIgnoreCase( requestedUri, OTF ) ||
+ StringUtils.endsWithIgnoreCase( requestedUri, EOT ) ||
+ StringUtils.endsWithIgnoreCase( requestedUri, WOFF ) ||
+ StringUtils.endsWithIgnoreCase( requestedUri, FONT_CSS ) ||
+ StringUtils.endsWithIgnoreCase( requestedUri, CSS ) ) {
+ return true;
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/gh-2225/src/main/java/com/edlogics/common/logging/domain/LoggingEvent.java b/gh-2225/src/main/java/com/edlogics/common/logging/domain/LoggingEvent.java
new file mode 100644
index 0000000..7889bfc
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/common/logging/domain/LoggingEvent.java
@@ -0,0 +1,361 @@
+/*
+ *
+ * Copyright EdLogics, LLC. All Rights Reserved.
+ *
+ * This software is the proprietary information of EdLogics, LLC.
+ * Use is subject to license terms.
+ *
+ */
+package com.edlogics.common.logging.domain;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.Transient;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+/**
+ * @author jlanpher
+ *
+ */
+@Entity
+@Table(name = "logging_event")
+public class LoggingEvent implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @Column(name = "event_id")
+ private Long id;
+
+ @Column(name = "timestmp")
+ private Long timeStamp;
+
+ @Column(name = "formatted_message")
+ private String formattedMessage;
+
+ @Column(name = "logger_name")
+ private String loggerName;
+
+ @Column(name = "level_string")
+ private String levelString;
+
+ @Column(name = "thread_name")
+ private String threadName;
+
+ @Column(name = "reference_flag")
+ private Integer referenceFlag;
+
+ @Column(name = "arg0")
+ private String arg0;
+
+ @Column(name = "arg1")
+ private String arg1;
+
+ @Column(name = "arg2")
+ private String arg2;
+
+ @Column(name = "arg3")
+ private String arg3;
+
+ @Column(name = "caller_filename")
+ private String callerFileName;
+
+ @Column(name = "caller_class")
+ private String callerClass;
+
+ @Column(name = "caller_method")
+ private String callerMethod;
+
+ @Column(name = "caller_line")
+ private String callerLine;
+
+ @OneToMany(mappedBy = "loggingEvent", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
+ private List loggingEventExceptions;
+
+ @OneToMany(mappedBy = "loggingEvent", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
+ private List loggingEventProperties;
+
+ public LoggingEvent() {
+ super();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder( 17, 37 )
+ .append( id )
+ .toHashCode();
+ }
+
+ @Override
+ public boolean equals( Object obj ) {
+ if ( !( obj instanceof LoggingEvent ) ) {
+ return false;
+ }
+ LoggingEvent ua = (LoggingEvent) obj;
+ return new EqualsBuilder()
+ .append( id, ua.id )
+ .isEquals();
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder( this )
+ .append( "id", getId() )
+ .toString();
+ }
+
+ @Transient
+ public Date getDateOccurred() {
+ return new Date( this.timeStamp );
+ }
+
+ /**
+ * @return the id
+ */
+ public Long getId() {
+ return id;
+ }
+
+ /**
+ * @param id the id to set
+ */
+ public void setId( Long id ) {
+ this.id = id;
+ }
+
+ /**
+ * @return the timeStamp
+ */
+ public Long getTimeStamp() {
+ return timeStamp;
+ }
+
+ /**
+ * @param timeStamp the timeStamp to set
+ */
+ public void setTimeStamp( Long timeStamp ) {
+ this.timeStamp = timeStamp;
+ }
+
+ /**
+ * @return the formattedMessage
+ */
+ public String getFormattedMessage() {
+ return formattedMessage;
+ }
+
+ /**
+ * @param formattedMessage the formattedMessage to set
+ */
+ public void setFormattedMessage( String formattedMessage ) {
+ this.formattedMessage = formattedMessage;
+ }
+
+ /**
+ * @return the loggerName
+ */
+ public String getLoggerName() {
+ return loggerName;
+ }
+
+ /**
+ * @param loggerName the loggerName to set
+ */
+ public void setLoggerName( String loggerName ) {
+ this.loggerName = loggerName;
+ }
+
+ /**
+ * @return the levelString
+ */
+ public String getLevelString() {
+ return levelString;
+ }
+
+ /**
+ * @param levelString the levelString to set
+ */
+ public void setLevelString( String levelString ) {
+ this.levelString = levelString;
+ }
+
+ /**
+ * @return the threadName
+ */
+ public String getThreadName() {
+ return threadName;
+ }
+
+ /**
+ * @param threadName the threadName to set
+ */
+ public void setThreadName( String threadName ) {
+ this.threadName = threadName;
+ }
+
+ /**
+ * @return the referenceFlag
+ */
+ public Integer getReferenceFlag() {
+ return referenceFlag;
+ }
+
+ /**
+ * @param referenceFlag the referenceFlag to set
+ */
+ public void setReferenceFlag( Integer referenceFlag ) {
+ this.referenceFlag = referenceFlag;
+ }
+
+ /**
+ * @return the arg0
+ */
+ public String getArg0() {
+ return arg0;
+ }
+
+ /**
+ * @param arg0 the arg0 to set
+ */
+ public void setArg0( String arg0 ) {
+ this.arg0 = arg0;
+ }
+
+ /**
+ * @return the arg1
+ */
+ public String getArg1() {
+ return arg1;
+ }
+
+ /**
+ * @param arg1 the arg1 to set
+ */
+ public void setArg1( String arg1 ) {
+ this.arg1 = arg1;
+ }
+
+ /**
+ * @return the arg2
+ */
+ public String getArg2() {
+ return arg2;
+ }
+
+ /**
+ * @param arg2 the arg2 to set
+ */
+ public void setArg2( String arg2 ) {
+ this.arg2 = arg2;
+ }
+
+ /**
+ * @return the arg3
+ */
+ public String getArg3() {
+ return arg3;
+ }
+
+ /**
+ * @param arg3 the arg3 to set
+ */
+ public void setArg3( String arg3 ) {
+ this.arg3 = arg3;
+ }
+
+ /**
+ * @return the callerFileName
+ */
+ public String getCallerFileName() {
+ return callerFileName;
+ }
+
+ /**
+ * @param callerFileName the callerFileName to set
+ */
+ public void setCallerFileName( String callerFileName ) {
+ this.callerFileName = callerFileName;
+ }
+
+ /**
+ * @return the callerClass
+ */
+ public String getCallerClass() {
+ return callerClass;
+ }
+
+ /**
+ * @param callerClass the callerClass to set
+ */
+ public void setCallerClass( String callerClass ) {
+ this.callerClass = callerClass;
+ }
+
+ /**
+ * @return the callerMethod
+ */
+ public String getCallerMethod() {
+ return callerMethod;
+ }
+
+ /**
+ * @param callerMethod the callerMethod to set
+ */
+ public void setCallerMethod( String callerMethod ) {
+ this.callerMethod = callerMethod;
+ }
+
+ /**
+ * @return the callerLine
+ */
+ public String getCallerLine() {
+ return callerLine;
+ }
+
+ /**
+ * @param callerLine the callerLine to set
+ */
+ public void setCallerLine( String callerLine ) {
+ this.callerLine = callerLine;
+ }
+
+ /**
+ * @return the loggingEventExceptions
+ */
+ public List getLoggingEventExceptions() {
+ return loggingEventExceptions;
+ }
+
+ /**
+ * @param loggingEventExceptions the loggingEventExceptions to set
+ */
+ public void setLoggingEventExceptions( List loggingEventExceptions ) {
+ this.loggingEventExceptions = loggingEventExceptions;
+ }
+
+ /**
+ * @return the loggingEventProperties
+ */
+ public List getLoggingEventProperties() {
+ return loggingEventProperties;
+ }
+
+ /**
+ * @param loggingEventProperties the loggingEventProperties to set
+ */
+ public void setLoggingEventProperties( List loggingEventProperties ) {
+ this.loggingEventProperties = loggingEventProperties;
+ }
+}
\ No newline at end of file
diff --git a/gh-2225/src/main/java/com/edlogics/common/logging/domain/LoggingEventException.java b/gh-2225/src/main/java/com/edlogics/common/logging/domain/LoggingEventException.java
new file mode 100644
index 0000000..5516f1c
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/common/logging/domain/LoggingEventException.java
@@ -0,0 +1,120 @@
+/*
+ *
+ * Copyright EdLogics, LLC. All Rights Reserved.
+ *
+ * This software is the proprietary information of EdLogics, LLC.
+ * Use is subject to license terms.
+ *
+ */
+package com.edlogics.common.logging.domain;
+
+import java.io.Serializable;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+/**
+ * @author jlanpher
+ *
+ */
+@Entity
+@Table(name = "logging_event_exception")
+public class LoggingEventException implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @Column(name = "i")
+ private Integer id;
+
+ @Column(name = "trace_line")
+ private String traceLine;
+
+ @ManyToOne
+ @Id
+ @JoinColumn(name = "event_id", referencedColumnName = "event_id")
+ private LoggingEvent loggingEvent;
+
+ public LoggingEventException() {
+ super();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder( 17, 37 )
+ .append( id )
+ .append( loggingEvent )
+ .toHashCode();
+ }
+
+ @Override
+ public boolean equals( Object obj ) {
+ if ( !( obj instanceof LoggingEventException ) ) {
+ return false;
+ }
+ LoggingEventException ua = (LoggingEventException) obj;
+ return new EqualsBuilder()
+ .append( id, ua.id )
+ .append( loggingEvent, ua.loggingEvent )
+ .isEquals();
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder( this )
+ .append( "id", id )
+ .append( "traceLine", traceLine )
+ .append( "loggingEvent", loggingEvent )
+ .toString();
+ }
+
+ /**
+ * @return the id
+ */
+ public Integer getId() {
+ return id;
+ }
+
+ /**
+ * @param id the id to set
+ */
+ public void setId( Integer id ) {
+ this.id = id;
+ }
+
+ /**
+ * @return the traceLine
+ */
+ public String getTraceLine() {
+ return traceLine;
+ }
+
+ /**
+ * @param traceLine the traceLine to set
+ */
+ public void setTraceLine( String traceLine ) {
+ this.traceLine = traceLine;
+ }
+
+ /**
+ * @return the loggingEvent
+ */
+ public LoggingEvent getLoggingEvent() {
+ return loggingEvent;
+ }
+
+ /**
+ * @param loggingEvent the loggingEvent to set
+ */
+ public void setLoggingEvent( LoggingEvent loggingEvent ) {
+ this.loggingEvent = loggingEvent;
+ }
+}
\ No newline at end of file
diff --git a/gh-2225/src/main/java/com/edlogics/common/logging/domain/LoggingEventProperty.java b/gh-2225/src/main/java/com/edlogics/common/logging/domain/LoggingEventProperty.java
new file mode 100644
index 0000000..cd70b87
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/common/logging/domain/LoggingEventProperty.java
@@ -0,0 +1,120 @@
+/*
+ *
+ * Copyright EdLogics, LLC. All Rights Reserved.
+ *
+ * This software is the proprietary information of EdLogics, LLC.
+ * Use is subject to license terms.
+ *
+ */
+package com.edlogics.common.logging.domain;
+
+import java.io.Serializable;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+/**
+ * @author jlanpher
+ *
+ */
+@Entity
+@Table(name = "logging_event_property")
+public class LoggingEventProperty implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @Column(name = "mapped_key", nullable = false)
+ private String mappedKey;
+
+ @Column(name = "mapped_value")
+ private String mappedValue;
+
+ @ManyToOne
+ @Id
+ @JoinColumn(name = "event_id", referencedColumnName = "event_id")
+ private LoggingEvent loggingEvent;
+
+ public LoggingEventProperty() {
+ super();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder( 17, 37 )
+ .append( mappedKey )
+ .append( loggingEvent )
+ .toHashCode();
+ }
+
+ @Override
+ public boolean equals( Object obj ) {
+ if ( !( obj instanceof LoggingEventProperty ) ) {
+ return false;
+ }
+ LoggingEventProperty ua = (LoggingEventProperty) obj;
+ return new EqualsBuilder()
+ .append( mappedKey, ua.mappedKey )
+ .append( loggingEvent, ua.loggingEvent )
+ .isEquals();
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder( this )
+ .append( "mappedKey", mappedKey )
+ .append( "mappedValue", mappedValue )
+ .append( "loggingEvent", loggingEvent )
+ .toString();
+ }
+
+ /**
+ * @return the mappedKey
+ */
+ public String getMappedKey() {
+ return mappedKey;
+ }
+
+ /**
+ * @param mappedKey the mappedKey to set
+ */
+ public void setMappedKey( String mappedKey ) {
+ this.mappedKey = mappedKey;
+ }
+
+ /**
+ * @return the mappedValue
+ */
+ public String getMappedValue() {
+ return mappedValue;
+ }
+
+ /**
+ * @param mappedValue the mappedValue to set
+ */
+ public void setMappedValue( String mappedValue ) {
+ this.mappedValue = mappedValue;
+ }
+
+ /**
+ * @return the loggingEvent
+ */
+ public LoggingEvent getLoggingEvent() {
+ return loggingEvent;
+ }
+
+ /**
+ * @param loggingEvent the loggingEvent to set
+ */
+ public void setLoggingEvent( LoggingEvent loggingEvent ) {
+ this.loggingEvent = loggingEvent;
+ }
+}
\ No newline at end of file
diff --git a/gh-2225/src/main/java/com/edlogics/common/model/MatchingEmailAddress.java b/gh-2225/src/main/java/com/edlogics/common/model/MatchingEmailAddress.java
new file mode 100644
index 0000000..77bc82d
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/common/model/MatchingEmailAddress.java
@@ -0,0 +1,22 @@
+/*
+ *
+ * Copyright EdLogics, LLC. All Rights Reserved.
+ *
+ * This software is the proprietary information of EdLogics, LLC.
+ * Use is subject to license terms.
+ *
+ */
+package com.edlogics.common.model;
+
+import java.io.Serializable;
+
+/**
+ * @author jlanpher
+ *
+ */
+public interface MatchingEmailAddress extends Serializable {
+
+ String getEmail();
+
+ String getEmailConfirm();
+}
\ No newline at end of file
diff --git a/gh-2225/src/main/java/com/edlogics/common/security/config/annotation/web/servlet/configuration/EnableWebMvcSecurity.java b/gh-2225/src/main/java/com/edlogics/common/security/config/annotation/web/servlet/configuration/EnableWebMvcSecurity.java
new file mode 100644
index 0000000..d90ee63
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/common/security/config/annotation/web/servlet/configuration/EnableWebMvcSecurity.java
@@ -0,0 +1,31 @@
+/*
+ *
+ * Copyright EdLogics, LLC. All Rights Reserved.
+ *
+ * This software is the proprietary information of EdLogics, LLC.
+ * Use is subject to license terms.
+ *
+ */
+package com.edlogics.common.security.config.annotation.web.servlet.configuration;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import org.springframework.context.annotation.Import;
+import org.springframework.security.config.annotation.web.servlet.configuration.WebMvcSecurityConfiguration;
+
+@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
+@Target(value = { java.lang.annotation.ElementType.TYPE })
+@Documented
+@Import(WebMvcSecurityConfiguration.class)
+@org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity
+/**
+ * Add this annotation to an {@code @Configuration} class to have the Spring Security
+ * configuration integrate with Spring MVC.
+ *
+ * @author edlogics
+ */
+public @interface EnableWebMvcSecurity {
+
+}
\ No newline at end of file
diff --git a/gh-2225/src/main/java/com/edlogics/common/theme/HostCookieThemeResolver.java b/gh-2225/src/main/java/com/edlogics/common/theme/HostCookieThemeResolver.java
new file mode 100644
index 0000000..75fe355
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/common/theme/HostCookieThemeResolver.java
@@ -0,0 +1,88 @@
+/*
+ *
+ * Copyright EdLogics, LLC. All Rights Reserved.
+ *
+ * This software is the proprietary information of EdLogics, LLC.
+ * Use is subject to license terms.
+ *
+ */
+package com.edlogics.common.theme;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.validator.routines.InetAddressValidator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.servlet.theme.CookieThemeResolver;
+import org.springframework.web.util.WebUtils;
+
+/**
+ * Extend CookieThemeResolver with the opportunity to resolve the theme from the host
+ * contained in the HttpServletRequest. This requires Spring themes to be named the same
+ * as the host name (in reverse), such as "com/edlogics/demo".
+ *
+ * If the subdomain contains a "-" then the subdomain will be split and the first part taken
+ * for the theme. So, halo-latest.edlogics.com would become com/edlogics/halo.
+ */
+public class HostCookieThemeResolver extends CookieThemeResolver {
+
+ private static final Logger LOG = LoggerFactory.getLogger( HostCookieThemeResolver.class );
+
+ @Override
+ public String resolveThemeName( HttpServletRequest request ) {
+
+ // Check request for preparsed or preset theme (copied from original Spring code).
+ String attr = (String) request.getAttribute( THEME_REQUEST_ATTRIBUTE_NAME );
+ LOG.debug( "Theme param (" + THEME_REQUEST_ATTRIBUTE_NAME + " is: " + attr );
+ if ( attr != null ) {
+ return attr;
+ }
+
+ // Retrieve cookie value from request (copied from original Spring code).
+ String cookieName = getCookieName();
+ Cookie cookie = WebUtils.getCookie( request, cookieName );
+ LOG.debug( "Theme cookie (" + cookieName + " is: " + cookie );
+ if ( cookie != null ) {
+ LOG.debug( "Cookie is: " + cookie.getValue() );
+ return cookie.getValue();
+ }
+
+ // Retrieve host value from request.
+ try {
+ URL url = new URL( ( request.getRequestURL() ).toString() );
+ if ( url.getHost() != null && !InetAddressValidator.getInstance().isValidInet4Address( url.getHost() ) ) {
+ LOG.debug( "Host is: " + url.getHost() );
+ // Convert the domain name to a reference to the theme message resource
+ // Reverse url parts (i.e. change demo.edlogics.com to com/edlogics/demo)
+ String[] parts = url.getHost().split( "\\." );
+
+ StringBuffer host = new StringBuffer();
+ for ( int i = parts.length - 1; i >= 0; i-- ) {
+ String part = parts[i];
+ // If (sub)domain name contains "-", take the first part to get the common theme
+ if ( i == 0 ) {
+ part = part.split( "-" )[0];
+ }
+ // Append with slashes
+ host.append( "/" ).append( part );
+ }
+ // Chop off first / char
+ String theme = host.toString().substring( 1 );
+ LOG.debug( "Theme (from host) is: " + theme );
+ return theme;
+ }
+ else {
+ LOG.error( "Host is null or IP Address in HttpServletRequest URL. Theme cannot be set. URL is: " + request.getRequestURL().toString() );
+ // Fall back to default theme.
+ return getDefaultThemeName();
+ }
+ } catch ( MalformedURLException e ) {
+ LOG.error( "URL is malformed. Theme cannot be set. URL is: " + request.getRequestURL().toString(), e );
+ return getDefaultThemeName();
+ }
+ }
+}
\ No newline at end of file
diff --git a/gh-2225/src/main/java/com/edlogics/common/theme/ReloadableResourceBundleThemeSource.java b/gh-2225/src/main/java/com/edlogics/common/theme/ReloadableResourceBundleThemeSource.java
new file mode 100644
index 0000000..1d57d81
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/common/theme/ReloadableResourceBundleThemeSource.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.edlogics.common.theme;
+
+import org.springframework.boot.bind.RelaxedPropertyResolver;
+import org.springframework.context.EnvironmentAware;
+import org.springframework.context.MessageSource;
+import org.springframework.context.support.ReloadableResourceBundleMessageSource;
+import org.springframework.core.env.Environment;
+import org.springframework.ui.context.support.ResourceBundleThemeSource;
+
+public class ReloadableResourceBundleThemeSource extends ResourceBundleThemeSource implements EnvironmentAware {
+
+ private RelaxedPropertyResolver environment;
+
+ @Override
+ public void setEnvironment( Environment environment ) {
+ this.environment = new RelaxedPropertyResolver( environment, "spring.messages." );
+ }
+
+ @Override
+ protected MessageSource createMessageSource( String basename ) {
+ ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
+ messageSource.setBasename( basename );
+ String encoding = this.environment.getProperty( "encoding", "utf-8" );
+ messageSource.setDefaultEncoding( encoding );
+ String cacheSeconds = this.environment.getProperty( "cache-seconds", "60" );
+ messageSource.setCacheSeconds( Integer.valueOf( cacheSeconds ) );
+ messageSource.setFallbackToSystemLocale( false );
+ return messageSource;
+ }
+}
\ No newline at end of file
diff --git a/gh-2225/src/main/java/com/edlogics/common/util/CollectionUtils.java b/gh-2225/src/main/java/com/edlogics/common/util/CollectionUtils.java
new file mode 100644
index 0000000..204d51b
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/common/util/CollectionUtils.java
@@ -0,0 +1,86 @@
+/*
+ *
+ * Copyright EdLogics, LLC. All Rights Reserved.
+ *
+ * This software is the proprietary information of EdLogics, LLC.
+ * Use is subject to license terms.
+ *
+ */
+package com.edlogics.common.util;
+
+import static ch.lambdaj.Lambda.exists;
+import static ch.lambdaj.Lambda.having;
+import static ch.lambdaj.Lambda.selectFirst;
+import static ch.lambdaj.collection.LambdaCollections.with;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.commons.beanutils.PropertyUtils;
+import org.hibernate.validator.constraints.NotEmpty;
+import org.springframework.validation.annotation.Validated;
+
+import ch.lambdaj.Lambda;
+import ch.lambdaj.function.convert.ArgumentConverter;
+
+/**
+ * Collection Utilities needed, but not found in any common libraries. e.g. Apache Commons
+ *
+ * @author Christopher Savory
+ *
+ */
+@Validated
+public class CollectionUtils {
+
+ /**
+ * Takes an original list and figures out how it was updated (added, updated, removed) using the
+ * argument.
+ *
+ * This will not return a new list, but rather return the original. Meaning it is meant to be used
+ * with an api like JPA where you would want a removal to be observed.
+ *
+ * @param originalCollection
+ * @param newCollection
+ * @param argument An argument defined using the {@link Lambda#on(Class)} method
+ * @param propertiesToCopy an array of strings that represents properties that get copied over for items that match the comparisonProperty
+ * @return
+ */
+ public static Collection diffAndMergeCollection( @NotEmpty Collection originalCollection, @NotEmpty Collection newCollection, A comparisonProperty,
+ @NotEmpty String... propertiesToCopy ) {
+ Collection newCollectionCopy = with( newCollection ).clone();
+
+ //First Remove the objects that are not in the new list
+ for ( Iterator it = originalCollection.iterator(); it.hasNext(); ) {
+ T object = it.next();
+ if ( !exists( newCollection, having( comparisonProperty, is( equalTo( new ArgumentConverter( comparisonProperty ).convert( object ) ) ) ) ) ) {
+ it.remove();
+ }
+ }
+
+ //Next, copy properties on objects that match
+ for ( Iterator it = newCollectionCopy.iterator(); it.hasNext(); ) {
+ T newObject = it.next();
+ T originalObject = selectFirst( originalCollection, having( comparisonProperty, is( equalTo( new ArgumentConverter( comparisonProperty ).convert( newObject ) ) ) ) );
+
+ if ( originalObject != null ) {
+ for ( String property : propertiesToCopy ) {
+ try {
+ PropertyUtils.setProperty( originalObject, property, PropertyUtils.getProperty( newObject, property ) );
+ } catch ( IllegalAccessException | InvocationTargetException | NoSuchMethodException e ) {
+ throw new RuntimeException( e );
+ }
+ }
+ it.remove();
+ }
+ }
+
+ //Add the remaining new properties to the original collection
+ originalCollection.addAll( newCollectionCopy );
+
+ return originalCollection;
+ }
+
+}
diff --git a/gh-2225/src/main/java/com/edlogics/common/util/HibernateUtil.java b/gh-2225/src/main/java/com/edlogics/common/util/HibernateUtil.java
new file mode 100644
index 0000000..52b2151
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/common/util/HibernateUtil.java
@@ -0,0 +1,42 @@
+/*
+ *
+ * Copyright EdLogics, LLC. All Rights Reserved.
+ *
+ * This software is the proprietary information of EdLogics, LLC.
+ * Use is subject to license terms.
+ *
+ */
+package com.edlogics.common.util;
+
+import org.hibernate.Hibernate;
+import org.hibernate.proxy.HibernateProxy;
+
+/**
+ * A set of Hibernate utilities
+ *
+ * @author Christopher Savory
+ *
+ */
+public class HibernateUtil {
+
+ /**
+ * Unwraps a proxied entity object.
+ * Useful for EntityLinks which cannot recognize a proxied (layz) entity.
+ *
+ * @param entity
+ * @return
+ */
+ public static T unproxy( T entity ) {
+ if ( entity == null ) {
+ return null;
+ }
+
+ if ( entity instanceof HibernateProxy ) {
+ Hibernate.initialize( entity );
+ entity = (T) ( (HibernateProxy) entity ).getHibernateLazyInitializer().getImplementation();
+ }
+
+ return entity;
+ }
+
+}
diff --git a/gh-2225/src/main/java/com/edlogics/common/util/RestUtils.java b/gh-2225/src/main/java/com/edlogics/common/util/RestUtils.java
new file mode 100644
index 0000000..b309a67
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/common/util/RestUtils.java
@@ -0,0 +1,32 @@
+/*
+ *
+ * Copyright EdLogics, LLC. All Rights Reserved.
+ *
+ * This software is the proprietary information of EdLogics, LLC.
+ * Use is subject to license terms.
+ *
+ */
+package com.edlogics.common.util;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+
+import edu.emory.mathcs.backport.java.util.Arrays;
+
+/**
+ * @author jlanpher
+ *
+ */
+public class RestUtils {
+
+ private RestUtils() {
+ super();
+ }
+
+ @SuppressWarnings("unchecked")
+ public static HttpHeaders generateHttpHeaders( MediaType... mediaTypes ) {
+ HttpHeaders headers = new HttpHeaders();
+ headers.setAccept( Arrays.asList( mediaTypes ) );
+ return headers;
+ }
+}
\ No newline at end of file
diff --git a/gh-2225/src/main/java/com/edlogics/common/util/StringUtils.java b/gh-2225/src/main/java/com/edlogics/common/util/StringUtils.java
new file mode 100644
index 0000000..06e790e
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/common/util/StringUtils.java
@@ -0,0 +1,44 @@
+/*
+ *
+ * Copyright EdLogics, LLC. All Rights Reserved.
+ *
+ * This software is the proprietary information of EdLogics, LLC.
+ * Use is subject to license terms.
+ *
+ */
+package com.edlogics.common.util;
+
+/**
+ * String Utilities needed, but not found in any common libraries. e.g. Apache Commons
+ *
+ * @author Christopher Savory
+ *
+ */
+public class StringUtils {
+
+ /**
+ * Converts a constant convention ("XXXXXXX_YYYY_ZZZ")
+ * to CamelCase ("xxxxxxxYyyyZzz")
+ *
+ * Note: could not find anywhere on any StringUtils
+ *
+ * @param cn
+ * @return
+ */
+ public static String convertToCamelCase( String cn ) {
+ StringBuilder sb = new StringBuilder();
+ String[] str = cn.split( "_" );
+ boolean firstTime = true;
+ for ( String temp : str ) {
+ if ( firstTime ) {
+ sb.append( temp.toLowerCase() );
+ firstTime = false;
+ } else {
+ sb.append( Character.toUpperCase( temp.charAt( 0 ) ) );
+ sb.append( temp.substring( 1 ).toLowerCase() );
+ }
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/gh-2225/src/main/java/com/edlogics/common/validation/util/ValidatorConstants.java b/gh-2225/src/main/java/com/edlogics/common/validation/util/ValidatorConstants.java
new file mode 100644
index 0000000..502de30
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/common/validation/util/ValidatorConstants.java
@@ -0,0 +1,70 @@
+/*
+ *
+ * Copyright EdLogics, LLC. All Rights Reserved.
+ *
+ * This software is the proprietary information of EdLogics, LLC.
+ * Use is subject to license terms.
+ *
+ */
+package com.edlogics.common.validation.util;
+
+/**
+ * @author jlanpher
+ *
+ * The purpose of this class is simply to provide a single location to
+ * define re-usable validation related constants.
+ *
+ */
+public final class ValidatorConstants {
+
+ //TODO: Please comment on what this pattern represents.
+ public static final String STANDARD_TEXT_VALIDATOR_255 = "(^(?!.{255})^(?!\\s*$).+)";
+
+ //TODO: Please comment on what this pattern represents.
+ public static final String STANDARD_TEXT_VALIDATOR_50 = "/^[a-zA-Z0-9*]{0,50}$/";
+
+ //TODO: Please comment on what this pattern represents.
+ public static final String ARBITRARY_PASSWORD_FOR_FORM_BINDING = "Suppr3ssed!";
+
+ //TODO: Please comment on what this pattern represents.
+ public static final String PASSWORD_REGEX = "((?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,20})";
+
+ /**
+ * Only contains '-', 'whitespace', 'aA'-'zZ' or apostrophe
+ */
+ public static final String NAME_REGEX = "^[-\\sa-zA-Z']+$";
+
+ /**
+ * Only contains numbers and letters'
+ */
+ public static final String SCREEN_NAME_REGEX = "^[0-9a-zA-Z]+$";
+
+ /**
+ * Only contains letters'
+ */
+ public static final String ALPHA_REGEX = "^[a-zA-Z]+$";
+
+ /**
+ * Only contains uppercase letters with a length of 2
+ */
+ public static final String STATE_CODE_REGEX = "^[A-Z]{2}+$";
+
+ /**
+ * Only contains numbers and is between 10 and 11 in length
+ */
+ public static final String PHONE_REGEX = "^[0-9]{10,11}$";
+
+ /**
+ * only contains letters, spaces, dashes apostrophe between 2 and 30 characters
+ * http://blog.letterstream.com/2014/03/19/whats-the-longest-city-name-in-the-u-s/
+ */
+ public static final String CITY_REGEX = "^[-\\.\\sa-zA-Z']{2,30}$";
+
+ /**
+ * only contains letters, spaces, comma, period, question mark, percent, plus, dollar, parentheses, semicolon, colon, dash, underscore, single quote, double
+ * quote, greater than, less than and forward slash
+ */
+ public static final String QUESTION_ANSWER_TEXT_REGEX = "^[\\(\\)\\<\\>\\,_\\-\\.\\sa-zA-Z0-9'\"\\/\\?\\$%\\+:;]+$";
+
+ private ValidatorConstants() {}
+}
diff --git a/gh-2225/src/main/java/com/edlogics/elrc/config/ApplicationSettings.java b/gh-2225/src/main/java/com/edlogics/elrc/config/ApplicationSettings.java
new file mode 100644
index 0000000..4513ddd
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/elrc/config/ApplicationSettings.java
@@ -0,0 +1,254 @@
+/*
+ *
+ * Copyright EdLogics, LLC. All Rights Reserved.
+ *
+ * This software is the proprietary information of EdLogics, LLC.
+ * Use is subject to license terms.
+ *
+ */
+package com.edlogics.elrc.config;
+
+import javax.annotation.Resource;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.core.env.Environment;
+import org.springframework.stereotype.Component;
+
+/**
+ * Settings that are configured external to the application
+ * Most likely these will come from application.yml
+ *
+ *
+ * @author csavory
+ *
+ */
+@Component
+public class ApplicationSettings implements ApplicationContextAware {
+
+ /**
+ * Used to access properties and profiles
+ */
+ private static Environment environment;
+
+ /**
+ * static reference so POJO's can reference the properties too
+ */
+ public static ApplicationSettings SETTINGS;
+
+ /**
+ * Autowired inner class that contains application media specific props
+ */
+ @Resource
+ protected ApplicationMediaProperties applicationMediaConfig;
+
+ /**
+ * Autowired inner class that contains application specific props
+ */
+ @Resource
+ protected ApplicationProperties applicationConfig;
+
+ @Value("${app.session-timeout}")
+ private String timeoutMinutes;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
+ */
+ @Override
+ public void setApplicationContext( ApplicationContext context ) throws BeansException {
+ SETTINGS = context.getBean( ApplicationSettings.class );
+
+ environment = context.getEnvironment();
+ }
+
+ /**
+ * @return the ApplicationMediaProperties object
+ */
+ public ApplicationMediaProperties getApplicationMediaConfig() {
+ return applicationMediaConfig;
+ }
+
+ /**
+ * @return the ApplicationMediaProperties object
+ */
+ public ApplicationProperties getApplicationConfig() {
+ return applicationConfig;
+ }
+
+ /**
+ * media specific application settings
+ */
+ @Component
+ @ConfigurationProperties("app.media")
+ public static class ApplicationMediaProperties {
+
+ private String urlRoot;
+
+ private String avatarThumbDir;
+
+ private String audioHead2headDir;
+
+ private String badgesDir;
+
+ private String emmiBaseUrl;
+
+ /**
+ * @return the urlRoot
+ */
+ public String getUrlRoot() {
+ return urlRoot;
+ }
+
+ /**
+ * @param username the urlRoot to set
+ */
+ public void setUrlRoot( String urlRoot ) {
+ this.urlRoot = urlRoot;
+ }
+
+ /**
+ * @return the avatarThumbDir
+ */
+ public String getAvatarThumbnailDirectory() {
+ return avatarThumbDir;
+ }
+
+ /**
+ * @param avatarThumbDir the avatarThumbDir to set
+ */
+ public void setAvatarThumbDir( String avatarThumbDir ) {
+ this.avatarThumbDir = avatarThumbDir;
+ }
+
+ /**
+ * @return the audioHead2headDir
+ */
+ public String getAudioHead2headDirectory() {
+ return audioHead2headDir;
+ }
+
+ /**
+ * @param audioHead2headDir the audioHead2headDir to set
+ */
+ public void setAudioHead2headDir( String audioHead2headDir ) {
+ this.audioHead2headDir = audioHead2headDir;
+ }
+
+ /**
+ * @return the badgesDir
+ */
+ public String getBadgesDirectory() {
+ return badgesDir;
+ }
+
+ /**
+ * @param badgesDir the badgesDir to set
+ */
+ public void setBadgesDir( String badgesDir ) {
+ this.badgesDir = badgesDir;
+ }
+
+ /**
+ * @return
+ */
+ public String getEmmiBaseUrl() {
+ return emmiBaseUrl;
+ }
+
+ /**
+ * @param emmiBaseUrl
+ */
+ public void setEmmiBaseUrl( String emmiBaseUrl ) {
+ this.emmiBaseUrl = emmiBaseUrl;
+ }
+ }
+
+ /**
+ * application settings
+ */
+ @Component
+ @ConfigurationProperties("app")
+ public static class ApplicationProperties {
+
+ private String baseUrl;
+ private String googleAnalyticsTrackingId;
+
+ /**
+ * @return the urlRoot
+ */
+ public String getBaseUrl() {
+ return baseUrl;
+ }
+
+ /**
+ * @param baseUrl
+ * set the parameter passed in
+ */
+ public void setBaseUrl( String baseUrl ) {
+ this.baseUrl = baseUrl;
+ }
+
+ /**
+ * @return the googleAnalyticsTrackingId
+ */
+ public String getGoogleAnalyticsTrackingId() {
+ return googleAnalyticsTrackingId;
+ }
+
+ /**
+ * @param googleAnalyticsTrackingId
+ * set the parameter passed in
+ */
+ public void setGoogleAnalyticsTrackingId(String googleAnalyticsTrackingId) {
+ this.googleAnalyticsTrackingId = googleAnalyticsTrackingId;
+ }
+
+ }
+
+ public static boolean isTest() {
+ return environment.acceptsProfiles( "test" );
+ }
+
+ public static boolean isLocal() {
+ return environment.acceptsProfiles( "local" );
+ }
+
+ public static boolean isLatest() {
+ return environment.acceptsProfiles( "latest" );
+ }
+
+ public static boolean isStage() {
+ return environment.acceptsProfiles( "stage" );
+ }
+
+ public static boolean isDemo() {
+ return environment.acceptsProfiles( "demo" );
+ }
+
+ public static boolean isProduction() {
+ return environment.acceptsProfiles( "production" );
+ }
+
+ public static String[] getActiveProfiles() {
+ return environment.getActiveProfiles();
+ }
+
+ /**
+ * @return the timeoutMinutes
+ */
+ public String getTimeoutMinutes() {
+ return timeoutMinutes;
+ }
+
+ /**
+ * @param timeoutMinutes the timeoutMinutes to set
+ */
+ public void setTimeoutMinutes( String timeoutMinutes ) {
+ this.timeoutMinutes = timeoutMinutes;
+ }
+}
\ No newline at end of file
diff --git a/gh-2225/src/main/java/com/edlogics/elrc/config/LoggingConfig.java b/gh-2225/src/main/java/com/edlogics/elrc/config/LoggingConfig.java
new file mode 100644
index 0000000..ab2d0dc
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/elrc/config/LoggingConfig.java
@@ -0,0 +1,51 @@
+package com.edlogics.elrc.config;
+
+import javax.annotation.Resource;
+import javax.sql.DataSource;
+
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.db.DBAppender;
+import ch.qos.logback.classic.filter.LevelFilter;
+import ch.qos.logback.core.db.DataSourceConnectionSource;
+import ch.qos.logback.core.spi.FilterReply;
+
+@Configuration
+@Profile("!test")
+public class LoggingConfig {
+
+ @Resource
+ private DataSource dataSource;
+
+ @Bean
+ public Logger logger() {
+
+ Logger logger = (Logger) LoggerFactory.getLogger( Logger.ROOT_LOGGER_NAME );
+ LevelFilter filter = new LevelFilter();
+ filter.setLevel( Level.ERROR );
+ filter.setOnMatch( FilterReply.ACCEPT );
+ filter.setOnMismatch( FilterReply.DENY );
+
+ DataSourceConnectionSource connSource = new DataSourceConnectionSource();
+ connSource.setDataSource( dataSource );
+ connSource.setContext( logger.getLoggerContext() );
+ connSource.start();
+
+ DBAppender dbAppender = new DBAppender();
+ dbAppender.setName( "DB" );
+ dbAppender.setConnectionSource( connSource );
+ dbAppender.setContext( logger.getLoggerContext() );
+ dbAppender.addFilter( filter );
+ dbAppender.start();
+
+ logger.setLevel( Level.ERROR );
+ logger.addAppender( dbAppender );
+
+ return logger;
+ }
+}
\ No newline at end of file
diff --git a/gh-2225/src/main/java/com/edlogics/elrc/config/MessageSourceConfiguration.java b/gh-2225/src/main/java/com/edlogics/elrc/config/MessageSourceConfiguration.java
new file mode 100644
index 0000000..8f2129a
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/elrc/config/MessageSourceConfiguration.java
@@ -0,0 +1,210 @@
+/*
+ *
+ * Copyright EdLogics, LLC. All Rights Reserved.
+ *
+ * This software is the proprietary information of EdLogics, LLC.
+ * Use is subject to license terms.
+ *
+ */
+package com.edlogics.elrc.config;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.ResourceBundle;
+import java.util.Set;
+
+import org.springframework.boot.bind.RelaxedPropertyResolver;
+import org.springframework.context.EnvironmentAware;
+import org.springframework.context.MessageSource;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.support.ReloadableResourceBundleMessageSource;
+import org.springframework.context.support.ResourceBundleMessageSource;
+import org.springframework.core.env.Environment;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+import com.fasterxml.jackson.core.JsonGenerationException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Environment specific configuration for the CustomResourceBundle.
+ * See below for comments on that class
+ *
+ * @author csavory
+ *
+ */
+@Configuration
+public class MessageSourceConfiguration implements EnvironmentAware {
+
+ private RelaxedPropertyResolver environment;
+
+ @Override
+ public void setEnvironment( Environment environment ) {
+ this.environment = new RelaxedPropertyResolver( environment, "spring.messages." );
+ }
+
+ @Bean
+ public MessageSource messageSource() {
+
+ boolean useReloadableMessageSource = Boolean.valueOf( this.environment.getProperty( "reloadable", "false" ) );
+ String[] baseNames = StringUtils.commaDelimitedListToStringArray( this.environment.getProperty( "basename", "messages" ) );
+ String encoding = this.environment.getProperty( "encoding", "utf-8" );
+ String cacheSeconds = this.environment.getProperty( "cache-seconds", "60" );
+
+ if ( useReloadableMessageSource ) {
+ CustomReloadableResourceBundle messageSource = new CustomReloadableResourceBundle();
+ messageSource.setBasenames( baseNames );
+ messageSource.setDefaultEncoding( encoding );
+ messageSource.setCacheSeconds( Integer.valueOf( cacheSeconds ) );
+ messageSource.setFallbackToSystemLocale( false );
+ return messageSource;
+ } else {
+ CustomResourceBundle messageSource = new CustomResourceBundle();
+ messageSource.setBasenames( baseNames );
+ messageSource.setDefaultEncoding( encoding );
+ messageSource.setFallbackToSystemLocale( false );
+ return messageSource;
+
+ }
+ }
+
+ /**
+ * An extension of ResourceBundleMessageSource that provides all messages. All Messages are cached.
+ *
+ */
+ public static class CustomResourceBundle extends ResourceBundleMessageSource {
+
+ public static final String ALL_MESSASGES = "all-messages-";
+ private Set baseNames;
+ private Map cachedData = new HashMap();
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ public CustomResourceBundle() {
+ super();
+ }
+
+ public Properties getMessagesForBasename( String basename, Locale locale ) {
+ String cacheKey = basename + locale.getCountry() + "+" + locale.getLanguage();
+ if ( cachedData.containsKey( cacheKey ) ) {
+ return cachedData.get( cacheKey );
+ }
+
+ ResourceBundle bundle = getResourceBundle( basename, locale );
+ Properties properties = convertResourceBundleToProperties( bundle );
+ cachedData.put( cacheKey, properties );
+ return properties;
+ }
+
+ public Properties getAllMessages( Locale locale ) {
+ String cacheKey = ALL_MESSASGES + locale.getCountry() + "+" + locale.getLanguage();
+ if ( cachedData.containsKey( cacheKey ) ) {
+ return cachedData.get( cacheKey );
+ }
+
+ Properties properties = new Properties();
+ for ( String baseName : baseNames ) {
+ properties.putAll( getMessagesForBasename( baseName, locale ) );
+ }
+ cachedData.put( cacheKey, properties );
+ return properties;
+ }
+
+ public Properties getMessages( Locale locale, String... propertyNames ) {
+ Properties filteredProperties = new Properties();
+ Properties allProperties = getAllMessages( locale );
+
+ for ( String key : propertyNames ) {
+ if ( allProperties.containsKey( key ) ) {
+ filteredProperties.put( key, allProperties.getProperty( key ) );
+ }
+ }
+
+ return filteredProperties;
+ }
+
+ public String getMessagesAsJsonString( Locale locale ) throws JsonGenerationException, JsonMappingException, IOException {
+ return mapper.writeValueAsString( getAllMessages( locale ) );
+ }
+
+ @Override
+ public void setBasenames( String... basenames ) {
+ baseNames = new HashSet();
+ if ( basenames != null ) {
+ for ( int i = 0; i < basenames.length; i++ ) {
+ String basename = basenames[i];
+ Assert.hasText( basename, "Basename must not be empty" );
+ baseNames.add( basename.trim() );
+ }
+ }
+
+ super.setBasenames( basenames );
+ }
+
+ /**
+ * Convert ResourceBundle into a Properties object.
+ *
+ * @param resource a resource bundle to convert.
+ * @return Properties a properties version of the resource bundle.
+ */
+ private static Properties convertResourceBundleToProperties( ResourceBundle resource ) {
+ Properties properties = new Properties();
+
+ Enumeration keys = resource.getKeys();
+ while ( keys.hasMoreElements() ) {
+ String key = keys.nextElement();
+ properties.put( key, resource.getString( key ) );
+ }
+
+ return properties;
+ }
+ }
+
+ /**
+ * An extension of ReloadableResourceBundleMessageSource that provides all messages.
+ *
+ */
+ public static class CustomReloadableResourceBundle extends ReloadableResourceBundleMessageSource {
+
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ public CustomReloadableResourceBundle() {
+ super();
+ }
+
+ public Properties getAllMessages( Locale locale ) {
+ PropertiesHolder propertiesHolder = getMergedProperties( locale );
+ Properties properties = propertiesHolder.getProperties();
+
+ return properties;
+ }
+
+ public Properties getMessages( Locale locale, String... propertyNames ) {
+ Properties filteredProperties = new Properties();
+ Properties allProperties = getAllMessages( locale );
+
+ for ( String key : propertyNames ) {
+ if ( allProperties.containsKey( key ) ) {
+ filteredProperties.put( key, allProperties.getProperty( key ) );
+ }
+ }
+
+ return filteredProperties;
+ }
+
+ public String getMessagesAsJsonString( Locale locale ) throws JsonGenerationException, JsonMappingException, IOException {
+ return mapper.writeValueAsString( getAllMessages( locale ) );
+ }
+
+ @Override
+ protected PropertiesHolder getProperties( String filename ) {
+ return super.getProperties( "classpath:/" + filename );
+ }
+ }
+}
diff --git a/gh-2225/src/main/java/com/edlogics/elrc/config/MobileConfig.java b/gh-2225/src/main/java/com/edlogics/elrc/config/MobileConfig.java
new file mode 100644
index 0000000..dbe84e7
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/elrc/config/MobileConfig.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2012-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.edlogics.elrc.config;
+
+import javax.annotation.Resource;
+
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.bind.RelaxedPropertyResolver;
+import org.springframework.context.EnvironmentAware;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
+import org.springframework.core.env.Environment;
+import org.springframework.mobile.device.view.LiteDeviceDelegatingViewResolver;
+import org.springframework.web.servlet.ViewResolver;
+import org.springframework.web.servlet.view.InternalResourceViewResolver;
+import org.thymeleaf.spring4.view.ThymeleafViewResolver;
+
+/**
+ * {@link EnableAutoConfiguration Auto-configuration} for Spring Mobile's {@link LiteDeviceDelegatingViewResolver}. If {@link ThymeleafViewResolver} is
+ * available
+ * it is configured as the delegate view resolver. Otherwise, {@link InternalResourceViewResolver} is used as a fallback.
+ *
+ * @author Roy Clarkson
+ * @since 1.1.0
+ */
+@Configuration
+@ConditionalOnClass(LiteDeviceDelegatingViewResolver.class)
+public class MobileConfig implements EnvironmentAware {
+
+ @Resource(name = "thymeleafViewResolver")
+ private ThymeleafViewResolver viewResolver;
+
+ private RelaxedPropertyResolver environment;
+
+ @Override
+ public void setEnvironment( Environment environment ) {
+ this.environment = new RelaxedPropertyResolver( environment,
+ "spring.mobile.devicedelegatingviewresolver." );
+ }
+
+ @Bean(name = "deviceDelegatingViewResolver")
+ protected CustomDeviceDelegatingViewResolver getConfiguredViewResolver() {
+ CustomDeviceDelegatingViewResolver resolver = new CustomDeviceDelegatingViewResolver( viewResolver );
+ resolver.setNormalPrefix( environment.getProperty( "normal-prefix", "" ) );
+ resolver.setNormalSuffix( environment.getProperty( "normal-suffix", "" ) );
+ resolver.setMobilePrefix( environment.getProperty( "mobile-prefix", "mobile/" ) );
+ resolver.setMobileSuffix( environment.getProperty( "mobile-suffix", "" ) );
+ resolver.setTabletPrefix( environment.getProperty( "tablet-prefix", "tablet/" ) );
+ resolver.setTabletSuffix( environment.getProperty( "tablet-suffix", "" ) );
+ resolver.setOrder( viewResolver.getOrder() == Ordered.HIGHEST_PRECEDENCE ? Ordered.HIGHEST_PRECEDENCE : viewResolver.getOrder() - 1 );
+ resolver.setEnableFallback( true );
+ return resolver;
+ }
+
+ public static class CustomDeviceDelegatingViewResolver extends LiteDeviceDelegatingViewResolver {
+
+ /**
+ * @param delegate
+ */
+ public CustomDeviceDelegatingViewResolver( ViewResolver delegate ) {
+ super( delegate );
+ }
+
+ /**
+ * @param template
+ * @return
+ */
+ public String getDeviceResolvedTemplate( String template ) {
+ return getDeviceViewName( template );
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/gh-2225/src/main/java/com/edlogics/elrc/config/ThemeConfig.java b/gh-2225/src/main/java/com/edlogics/elrc/config/ThemeConfig.java
new file mode 100644
index 0000000..e33d2ab
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/elrc/config/ThemeConfig.java
@@ -0,0 +1,61 @@
+/*
+ *
+ * Copyright EdLogics, LLC. All Rights Reserved.
+ *
+ * This software is the proprietary information of EdLogics, LLC.
+ * Use is subject to license terms.
+ *
+ */
+package com.edlogics.elrc.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.ui.context.support.ResourceBundleThemeSource;
+import org.springframework.web.servlet.ThemeResolver;
+import org.springframework.web.servlet.theme.ThemeChangeInterceptor;
+
+import com.edlogics.common.theme.HostCookieThemeResolver;
+import com.edlogics.common.theme.ReloadableResourceBundleThemeSource;
+
+/**
+ * Configure Spring themes.
+ */
+@Configuration
+public class ThemeConfig {
+
+ /**
+ * Themes are found in "resources/themes/*.properties".
+ *
+ * @return The theme source.
+ */
+ @Bean
+ public ResourceBundleThemeSource themeSource() {
+ ResourceBundleThemeSource source = new ReloadableResourceBundleThemeSource();
+ source.setBasenamePrefix( "themes/" );
+ return source;
+ }
+
+ /**
+ * The param name to change a theme is "theme".
+ *
+ * @return The theme change interceptor.
+ */
+ @Bean
+ public ThemeChangeInterceptor themeChangeInterceptor() {
+ ThemeChangeInterceptor themeChangeInterceptor = new ThemeChangeInterceptor();
+ themeChangeInterceptor.setParamName( "theme" );
+ return themeChangeInterceptor;
+ }
+
+ /**
+ * The default theme is "localhost".
+ *
+ * @return The theme resolver.
+ */
+ @Bean
+ public ThemeResolver themeResolver() {
+ final HostCookieThemeResolver themeResolver = new HostCookieThemeResolver();
+ themeResolver.setDefaultThemeName( "localhost" );
+ return themeResolver;
+ }
+}
diff --git a/gh-2225/src/main/java/com/edlogics/elrc/config/WebConfig.java b/gh-2225/src/main/java/com/edlogics/elrc/config/WebConfig.java
new file mode 100644
index 0000000..b6f4c88
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/elrc/config/WebConfig.java
@@ -0,0 +1,212 @@
+/*
+ *
+ * Copyright EdLogics, LLC. All Rights Reserved.
+ *
+ * This software is the proprietary information of EdLogics, LLC.
+ * Use is subject to license terms.
+ *
+ */
+package com.edlogics.elrc.config;
+
+import java.net.MalformedURLException;
+import java.util.Locale;
+
+import javax.annotation.Resource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.bind.RelaxedPropertyResolver;
+import org.springframework.context.EnvironmentAware;
+import org.springframework.context.MessageSource;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
+import org.springframework.core.env.Environment;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.UrlResource;
+import org.springframework.mobile.device.DeviceResolver;
+import org.springframework.mobile.device.LiteDeviceResolver;
+import org.springframework.util.StringUtils;
+import org.springframework.validation.Validator;
+import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
+import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.servlet.LocaleResolver;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
+import org.springframework.web.servlet.i18n.CookieLocaleResolver;
+import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
+import org.thymeleaf.extras.conditionalcomments.dialect.ConditionalCommentsDialect;
+import org.thymeleaf.spring4.SpringTemplateEngine;
+import org.thymeleaf.spring4.view.AjaxThymeleafViewResolver;
+import org.thymeleaf.spring4.view.ThymeleafViewResolver;
+import org.thymeleaf.templateresolver.TemplateResolver;
+
+/**
+ * @author csavory
+ *
+ */
+@Configuration
+public class WebConfig extends WebMvcConfigurerAdapter implements EnvironmentAware {
+
+ @Resource
+ MessageSource messages;
+
+ @Autowired
+ SpringTemplateEngine springTemplateEngine;
+
+ private RelaxedPropertyResolver environment;
+
+ @Override
+ public void setEnvironment( Environment environment ) {
+ this.environment = new RelaxedPropertyResolver( environment,
+ "spring.mobile.devicedelegatingviewresolver." );
+ }
+
+ @Override
+ public void addInterceptors( InterceptorRegistry registry ) {
+
+ LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
+ localeChangeInterceptor.setParamName( "lang" );
+ registry.addInterceptor( localeChangeInterceptor );
+ }
+
+ @Bean
+ public LocaleResolver localeResolver() {
+
+ CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver();
+ cookieLocaleResolver.setDefaultLocale( StringUtils
+ .parseLocaleString( "en" ) );
+ return cookieLocaleResolver;
+ }
+
+ /**
+ * Needed to enable validation at the method level using Bean and Hibernate
+ * Validator.
+ *
+ * @return
+ */
+ @Bean
+ public MethodValidationPostProcessor getMethodValidationPostProcessor(
+ MessageSource messageSource ) {
+ MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
+ methodValidationPostProcessor
+ .setValidatorFactory( getLocalValidatorFactoryBean( messageSource ) );
+ return methodValidationPostProcessor;
+ }
+
+ /**
+ * The basic configuration above will trigger JSR-303 to initialize using
+ * its default bootstrap mechanism. A JSR-303 provider, such as Hibernate
+ * Validator, is expected to be present in the classpath and will be
+ * detected automatically.
+ *
+ * @return
+ */
+ @Bean(name = "validator")
+ public LocalValidatorFactoryBean getLocalValidatorFactoryBean(
+ MessageSource messageSource ) {
+ LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
+ validatorFactoryBean.setValidationMessageSource( messageSource );
+ return validatorFactoryBean;
+ }
+
+ @Bean(name = "restTemplate")
+ public RestTemplate getRestTemplate() {
+ return new RestTemplate();
+ }
+
+ @Override
+ public Validator getValidator() {
+ return getLocalValidatorFactoryBean( messages );
+ }
+
+ @Bean(name = "deviceResolver")
+ protected DeviceResolver getDeviceResolver() {
+ return new LiteDeviceResolver();
+ }
+
+ @Bean(name = "thymeleafViewResolver")
+ protected ThymeleafViewResolver getViewResolver() {
+
+ // Get new ViewResolver
+ ThymeleafViewResolver resolver = new CustomAjaxThymeleafViewResolver();
+ resolver.setTemplateEngine( springTemplateEngine );
+
+ // Setup encoding
+ String encoding = this.environment.getProperty( "encoding", "UTF-8" );
+ String type = this.environment.getProperty( "contentType", "text/html" );
+ if ( !type.contains( encoding ) )
+ type = type + ";charset=" + encoding;
+ resolver.setCharacterEncoding( encoding );
+ resolver.setContentType( type );
+
+ // Set names
+ resolver.setExcludedViewNames( this.environment.getProperty( "excludedViewNames", String[].class ) );
+ resolver.setViewNames( this.environment.getProperty( "viewNames", String[].class ) );
+
+ // This resolver acts as a fallback resolver (e.g. like a
+ // InternalResourceViewResolver) so it needs to have low precedence
+ resolver.setOrder( Ordered.LOWEST_PRECEDENCE - 5 );
+
+ return resolver;
+ }
+
+ public static class CustomAjaxThymeleafViewResolver extends AjaxThymeleafViewResolver {
+
+ @Value("${spring.mobile.thymeleaf.cache-fallback-result:true}")
+ public Boolean bCache;
+
+ @Autowired
+ private TemplateResolver templateResolver;
+
+ @Autowired
+ private SpringTemplateEngine templateEngine;
+
+ @Override
+ public boolean isCache() {
+ return bCache;
+ }
+
+ /**
+ * Override to actually look for the resource to see if it
+ * exists. This view resolver caches its views after the first time, so
+ * the overhead of this check may be acceptible.
+ */
+ @Override
+ protected boolean canHandle( final String viewName, final Locale locale ) {
+ Boolean exists = thymeleafViewExists( viewName );
+ return ( exists == null || exists ) ? super.canHandle( viewName, locale ) : exists;
+ }
+
+ protected Boolean thymeleafViewExists( String viewName ) {
+
+ // Ensure that the Engine/Resolver is initialized
+ templateEngine.initialize();
+
+ org.springframework.core.io.Resource res;
+ String viewPath = templateResolver.getPrefix() + viewName + templateResolver.getSuffix();
+
+ if ( viewPath.startsWith( "classpath:" ) )
+ res = new ClassPathResource( viewPath.substring( 10 ) );
+ else if ( viewPath.startsWith( "file:" ) )
+ res = new FileSystemResource( viewPath.substring( 5 ) );
+ else {
+ try {
+ res = new UrlResource( viewPath );
+ } catch ( MalformedURLException e ) {
+ logger.info( "Unrecognised resource " + viewName );
+ return null; // Can't decide, give up
+ }
+ }
+
+ return res.exists();
+ }
+ };
+
+ @Bean
+ public ConditionalCommentsDialect conditionalCommentsDialect() {
+ return new ConditionalCommentsDialect();
+ }
+}
\ No newline at end of file
diff --git a/gh-2225/src/main/java/com/edlogics/elrc/config/WebSecurityConfig.java b/gh-2225/src/main/java/com/edlogics/elrc/config/WebSecurityConfig.java
new file mode 100644
index 0000000..1cf14e3
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/elrc/config/WebSecurityConfig.java
@@ -0,0 +1,145 @@
+/*
+ *
+ * Copyright EdLogics, LLC. All Rights Reserved.
+ *
+ * This software is the proprietary information of EdLogics, LLC.
+ * Use is subject to license terms.
+ *
+ */
+package com.edlogics.elrc.config;
+
+import java.security.SecureRandom;
+
+import javax.annotation.Resource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+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.WebSecurityConfigurerAdapter;
+import org.springframework.security.core.token.KeyBasedPersistenceTokenService;
+import org.springframework.security.core.token.TokenService;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+
+import com.edlogics.common.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
+
+/**
+ * Customizes Spring Security configuration.
+ *
+ * @author Chris Savory
+ */
+@Configuration
+@EnableWebMvcSecurity
+@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
+public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+
+ private ApplicationContext context;
+
+ /**
+ * Want to make sure ApplicationSettings is configured first
+ */
+ @Resource
+ private ApplicationSettings applicationSettings;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#configure(
+ * org.springframework.security.config.annotation.web.builders.HttpSecurity)
+ */
+ @SuppressWarnings("static-access")
+ @Override
+ protected void configure( HttpSecurity http ) throws Exception {
+
+ // @formatter:off
+ http
+ .csrf()
+ .requireCsrfProtectionMatcher(new AntPathRequestMatcher("/oauth/authorize")).disable()
+
+ // See https://jira.springsource.org/browse/SPR-11496
+ // For IE8 and IE9 SockJS
+ .headers().addHeaderWriter(
+ new XFrameOptionsHeaderWriter(
+ XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)).and()
+
+ .formLogin()
+ .usernameParameter("j_username") /* BY DEFAULT IS username!!! */
+ .passwordParameter("j_password") /* BY DEFAULT IS password!!! */
+ .loginProcessingUrl("/j_spring_security_check")
+ .loginPage("/login.html")
+ .failureUrl("/login.html?error")
+ .defaultSuccessUrl("/")
+ .permitAll()
+ .and()
+ .logout()
+ .logoutSuccessUrl("/login?logout")
+ .logoutUrl("/logout")
+ .permitAll()
+ .and()
+ .authorizeRequests()
+ .antMatchers("/**").permitAll()
+ .antMatchers("/js/**").permitAll()
+ .antMatchers("/css/**").permitAll()
+ .antMatchers("/less/**").permitAll()
+ .antMatchers("/img/**").permitAll()
+ .antMatchers("/fonts/**").permitAll()
+ .antMatchers("/json/**").permitAll()
+ .antMatchers("/docs/**").permitAll()
+ .antMatchers("/sign-up/**").permitAll()
+ .antMatchers("/join/**").permitAll()
+ .antMatchers("/remote-user/**").permitAll()
+ .antMatchers("/admin/**").hasRole("ADMIN")
+ .antMatchers("/registration/**").permitAll()
+ .antMatchers("/profile-service/avatars").permitAll()
+ .antMatchers("/account/send-password-reset").permitAll()
+ .antMatchers("/account/reset-password/**").permitAll()
+ .antMatchers("/legal/terms").permitAll()
+ .antMatchers("/legal/privacy").permitAll()
+ .antMatchers("/help").permitAll()
+ .antMatchers("/rss").permitAll()
+ .antMatchers("/health").permitAll()
+ .anyRequest().authenticated()
+ .and();
+ // @formatter:on
+ }
+
+ @Override
+ public void configure( WebSecurity web ) throws Exception {
+ web.ignoring().antMatchers( "/oauth/uncache_approvals", "/oauth/cache_approvals" );
+ }
+
+ @Override
+ @Autowired
+ public void setApplicationContext( ApplicationContext context ) {
+ super.setApplicationContext( context );
+ this.context = context;
+ }
+
+ @Bean(name = "passwordEncoder")
+ public PasswordEncoder getPasswordEncoder() {
+ return new BCryptPasswordEncoder( 10 );
+ }
+
+ @SuppressWarnings("deprecation")
+ @Bean(name = "tokenService")
+ public TokenService getTokenService() {
+ KeyBasedPersistenceTokenService tokenService = new KeyBasedPersistenceTokenService();
+ tokenService.setServerSecret( "!3dL061c$#P7@tF0rW" );
+ tokenService.setSecureRandom( new SecureRandom() );
+ tokenService.setServerInteger( new Integer( 5178 ) );
+ /*
+ * Produces a token key roughly of a length == 280 characters.
+ *
+ * With a url length limitation of roughly 669 characters this is the maximum size token key to generate
+ * and still allow for 389 characters for the base url string and any additional parameters.
+ */
+ tokenService.setPseudoRandomNumberBits( 32 );
+ return tokenService;
+ }
+}
\ No newline at end of file
diff --git a/gh-2225/src/main/java/com/edlogics/elrc/config/WebSocketConfig.java b/gh-2225/src/main/java/com/edlogics/elrc/config/WebSocketConfig.java
new file mode 100644
index 0000000..90869bb
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/elrc/config/WebSocketConfig.java
@@ -0,0 +1,88 @@
+/*
+ *
+ * Copyright EdLogics, LLC. All Rights Reserved.
+ *
+ * This software is the proprietary information of EdLogics, LLC.
+ * Use is subject to license terms.
+ *
+ */
+package com.edlogics.elrc.config;
+
+import java.util.concurrent.Executor;
+
+import javax.annotation.Resource;
+import javax.servlet.ServletContext;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
+import org.springframework.messaging.simp.config.MessageBrokerRegistry;
+import org.springframework.messaging.support.AbstractSubscribableChannel;
+import org.springframework.messaging.support.ChannelInterceptorAdapter;
+import org.springframework.messaging.support.ExecutorSubscribableChannel;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.security.concurrent.DelegatingSecurityContextExecutor;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
+import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurationSupport;
+
+/**
+ * Customizes Spring WebSocket configuration.
+ *
+ * @author Christopher Savory
+ *
+ */
+@Configuration
+@EnableScheduling
+public class WebSocketConfig extends WebSocketMessageBrokerConfigurationSupport {
+
+ @Resource
+ private ServletContext servletContext;
+
+ protected Logger logger = LoggerFactory.getLogger( getClass() );
+
+ @Override
+ @Bean
+ public AbstractSubscribableChannel clientInboundChannel() {
+ Executor securityExecutor = new DelegatingSecurityContextExecutor( clientInboundChannelExecutor() );
+ ExecutorSubscribableChannel result = new ExecutorSubscribableChannel( securityExecutor );
+ result.addInterceptor( securityContextChannelInterceptorAdapter() );
+ return result;
+ }
+
+ @Bean
+ public ChannelInterceptorAdapter securityContextChannelInterceptorAdapter() {
+ return new ChannelInterceptorAdapter() {
+
+ @Override
+ public Message> preSend( Message> message, MessageChannel channel ) {
+ Authentication auth = (Authentication) SimpMessageHeaderAccessor.getUser( message.getHeaders() );
+ SecurityContextHolder.getContext().setAuthentication( auth );
+ return super.preSend( message, channel );
+ }
+
+ @Override
+ public void postSend( Message> message, MessageChannel channel, boolean sent ) {
+ //SecurityContextHolder.clearContext();
+ super.postSend( message, channel, sent );
+ }
+ };
+ }
+
+ @Override
+ public void registerStompEndpoints( StompEndpointRegistry registry ) {
+ registry.addEndpoint( "/head2head" ).withSockJS().setClientLibraryUrl( servletContext.getContextPath() + "/js/lib/sockjs/sockjs.min.js" );
+ }
+
+ @Override
+ public void configureMessageBroker( MessageBrokerRegistry registry ) {
+ registry.enableSimpleBroker( "/queue/" );
+ registry.setApplicationDestinationPrefixes( "/app" );
+ }
+
+}
\ No newline at end of file
diff --git a/gh-2225/src/main/java/com/edlogics/elrc/controller/IndexController.java b/gh-2225/src/main/java/com/edlogics/elrc/controller/IndexController.java
new file mode 100644
index 0000000..e742d52
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/elrc/controller/IndexController.java
@@ -0,0 +1,45 @@
+/*
+ *
+ * Copyright EdLogics, LLC. All Rights Reserved.
+ *
+ * This software is the proprietary information of EdLogics, LLC.
+ * Use is subject to license terms.
+ *
+ */
+package com.edlogics.elrc.controller;
+
+import java.util.Locale;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import com.edlogics.elrc.controller.model.FakeObject;
+
+@Controller
+@RequestMapping("/")
+public class IndexController {
+
+ protected Logger logger = LoggerFactory.getLogger( getClass() );
+
+ @Value("${app.timezone}")
+ String elrcTimezone;
+
+ /**
+ * Home page.
+ *
+ * @return The view name (an HTML page with Thymeleaf markup).
+ */
+ @RequestMapping({ "/", "/home" })
+ public @ResponseBody ResponseEntity home( HttpServletRequest request, HttpServletResponse response, Locale locale ) {
+ return new ResponseEntity<>( new FakeObject(), HttpStatus.OK );
+ }
+}
diff --git a/gh-2225/src/main/java/com/edlogics/elrc/controller/model/FakeObject.java b/gh-2225/src/main/java/com/edlogics/elrc/controller/model/FakeObject.java
new file mode 100644
index 0000000..645f3be
--- /dev/null
+++ b/gh-2225/src/main/java/com/edlogics/elrc/controller/model/FakeObject.java
@@ -0,0 +1,45 @@
+/*
+ *
+ * Copyright EdLogics, LLC. All Rights Reserved.
+ *
+ * This software is the proprietary information of EdLogics, LLC.
+ * Use is subject to license terms.
+ *
+ */
+package com.edlogics.elrc.controller.model;
+
+import java.util.Date;
+
+import org.joda.time.DateTime;
+import org.springframework.hateoas.ResourceSupport;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonFormat.Shape;
+
+/**
+ * @author Christopher Savory
+ *
+ */
+public class FakeObject extends ResourceSupport {
+
+ /**
+ *
+ */
+ public FakeObject() {
+ // TODO Auto-generated constructor stub
+ }
+
+ public Date getDate() {
+ return new Date();
+ }
+
+ public DateTime getJodaDate() {
+ return DateTime.now();
+ }
+
+ @JsonFormat(shape = Shape.STRING)
+ public DateTime getJodaDateForceAsString() {
+ return DateTime.now();
+ }
+
+}
diff --git a/gh-2225/src/main/resources/application.yml b/gh-2225/src/main/resources/application.yml
new file mode 100644
index 0000000..ceda4df
--- /dev/null
+++ b/gh-2225/src/main/resources/application.yml
@@ -0,0 +1,47 @@
+spring:
+ application:
+ name: JodaBootBugs
+ version: 1.0
+ thymeleaf:
+ prefix: classpath:/templates/
+ suffix: .html
+ mode: HTML5
+ cache: true
+ encoding: UTF-8
+ mobile:
+ devicedelegatingviewresolver:
+ enabled: true
+ resources:
+ cache-period: 3600
+ jackson:
+ date-format: com.fasterxml.jackson.databind.util.ISO8601DateFormat
+ serialization:
+ write-dates-as-timestamps: false
+flyway:
+ enabled: false
+ init-on-migrate: true
+ locations: db/migration/common,db/migration/postgres
+info:
+ build:
+ artifact=${project.artifactId}
+ name=${project.name}
+ description=${project.description}
+ version=${project.version}
+app:
+ timezone: America/New_York
+ media:
+ avatar-thumb-dir: /img/avatars/100x100/
+ audio-head2head-dir: /audio/head2head/
+ badges-dir: /img/badges/
+ emmi-base-url: https://stageemmi.emmisolutions.com
+ content:
+ dir: content
+ user-profile:
+ minimum-age: 10
+ maximum-age: 110
+ health-scratch:
+ timezone: America/New_York
+ reset-hour: 4
+ reset-minute: 0
+ session-timeout: 15
+
diff --git a/gh-2225/src/main/resources/logback.xml b/gh-2225/src/main/resources/logback.xml
new file mode 100644
index 0000000..3532a9d
--- /dev/null
+++ b/gh-2225/src/main/resources/logback.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+ ${FILE_LOG_PATTERN}
+
+ ${SQL_LOG_FILE}
+
+ ${SQL_LOG_FILE}.%i
+
+
+ 10MB
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gh-2225/src/main/web/test.txt b/gh-2225/src/main/web/test.txt
new file mode 100644
index 0000000..e69de29