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