diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af0875c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/ +*.iml +.gradle/ \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100755 index 0000000..b252130 --- /dev/null +++ b/build.gradle @@ -0,0 +1,164 @@ +buildscript { + repositories { + maven() { + url "https://ci.it.ewerk.com/nexus/content/groups/public" + } + } + dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:1.0.1.RELEASE") + + /* + * This configuration works. I can run 'gradle pitest' without problems. I also + * can run 'gradle bootRun' without problems. + * + * URL: http://localhost:8080/dashboard/index.html -> OK + * URL: http://localhost:8080/dashboard/api/greeting -> OK + */ + classpath("info.solidsoft.gradle.pitest:gradle-pitest-plugin:0.32.0") { + exclude group: "org.pitest" + } + classpath "org.pitest:pitest-command-line:0.33" + + /* + * Commenting out lines 17-20 and using 0.33.0-SNAPSHOT of the plugin breaks the build + * + * - pitest tasks is working + * - I cannot start the application because the configuration file is no more picked up. + * - moving the application.properties to project root folder workarounds this, + * but logging config is missing + * + * URL: http://localhost:8080/dashboard/index.html -> NOT_FOUND + * URL: http://localhost:8080/dashboard/api/greeting -> STILL OK + * + * It turns out the the pitest SNAPSHOT plugin somehow scrambles the builds setup, the whole + * 'src/main/resources' folder seems to be ignored and no more recognised by spring-boot. So + * all static resources are missing within the deployment. + * + * I do not think this is an issue with the spring-boot-gradle-plugin (not sure though), as + * with gradle-pitest-plugin-0.32.0 everything is working fine. + */ + //classpath("info.solidsoft.gradle.pitest:gradle-pitest-plugin:0.33.0-SNAPSHOT") + } +} + +apply plugin: 'java' +apply plugin: 'maven' +apply plugin: "pitest" +apply plugin: 'spring-boot' + +configurations { + all*.exclude(group: "commons-logging") + all*.exclude(group: "joda-time") +} + +ext { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + + assertjVersion = "1.5.0" + flywayGradleVersion = "2.3.1" + guavaVersion = "16.0.1" + hibernateVersion = "4.3.4.Final" + hibernateValidatorVersion = "5.1.0.Final" + hikariVersion = "1.3.5" + jacksonVersion = "2.3.1" + jettyVersion = "9.1.4.v20140401" + logbackVersion = "1.1.1" + mockitoVersion = "1.9.5" + mysqlVersion = "5.1.29" + pitestPluginVersion = "0.32.0" + queryDslVersion = "3.3.1" + slf4jVersion = "1.7.6" + springVersion = "4.0.3.RELEASE" + springBootVersion = "1.0.1.RELEASE" + springDataCommonsVersion = "1.7.1.RELEASE" + springDataJpaVersion = "1.5.2.RELEASE" + springSecurityVersion = "3.2.3.RELEASE" + testngVersion = "6.8.8" + validationVersion = "1.1.0.Final" +} + +repositories { + mavenCentral() +} + +dependencies { + // Spring Framework + compile "org.springframework:spring-aspects:" + springVersion + compile "org.springframework:spring-jdbc:" + springVersion + compile "org.springframework:spring-orm:" + springVersion + compile "org.springframework:spring-tx:" + springVersion + compile "org.springframework:spring-webmvc:" + springVersion + + // Spring Security + compile("org.springframework.security:spring-security-web:" + springSecurityVersion) { + exclude group: "org.springframework" + } + compile("org.springframework.security:spring-security-config:" + springSecurityVersion) { + exclude group: "org.springframework" + } + + // Spring boot + compile("org.springframework.boot:spring-boot-starter:" + springBootVersion) { + exclude module: "spring-boot-starter-logging" + } + compile("org.springframework.boot:spring-boot-starter-jetty:" + springBootVersion) { + exclude group: "org.eclipse.jetty" + } + + // JSR-303 + compile "javax.validation:validation-api:" + validationVersion + compile "org.hibernate:hibernate-validator:" + hibernateValidatorVersion + + // Guava + compile "com.google.guava:guava:" + guavaVersion + + // Jetty9 + compile "org.eclipse.jetty:jetty-webapp:" + jettyVersion + compile "org.eclipse.jetty:jetty-jsp:" + jettyVersion + + // Logging Stack + compile "org.slf4j:slf4j-api:" + slf4jVersion + compile "org.slf4j:jcl-over-slf4j:" + slf4jVersion + compile "org.slf4j:log4j-over-slf4j:" + slf4jVersion + compile "org.slf4j:jul-to-slf4j:" + slf4jVersion + compile "ch.qos.logback:logback-classic:" + logbackVersion + + // Jackson + compile "com.fasterxml.jackson.core:jackson-databind:" + jacksonVersion + compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:" + jacksonVersion + //compile "com.fasterxml.jackson.datatype:jackson-datatype-hibernate4:" + jacksonVersion + + // Testing + compile "org.testng:testng:" + testngVersion + compile "org.mockito:mockito-core:" + mockitoVersion + compile "org.assertj:assertj-core:" + assertjVersion +} + +jar { + baseName = 'gs-spring-boot' + version = '0.1.0' +} + +test { + useTestNG() + + minHeapSize = "128m" + maxHeapSize = "512m" +} + +pitest { + targetClasses = ['com.company.hello.*'] + threads = 6 +} + +jar { + manifest { + attributes( + "Implementation-Title": "com.company.hello", + "Implementation-Version": version, + "Built-JDK": System.getProperty("java.version"), + "Built-Gradle": gradle.gradleVersion + ) + } +} \ No newline at end of file diff --git a/src/main/java/com/company/hello/Application.java b/src/main/java/com/company/hello/Application.java new file mode 100755 index 0000000..a645277 --- /dev/null +++ b/src/main/java/com/company/hello/Application.java @@ -0,0 +1,28 @@ +package com.company.hello; + +import com.company.hello.security.SecurityConfiguration; +import com.company.hello.web.WebConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@ComponentScan +@EnableAutoConfiguration( + exclude = {MessageSourceAutoConfiguration.class, BatchAutoConfiguration.class}) +@Import({WebConfiguration.class, SecurityConfiguration.class}) +public class Application { + public static void main(String[] args) { + SpringApplication application = new SpringApplication(Application.class); + application.setShowBanner(false); + application.setHeadless(true); + application.setRegisterShutdownHook(true); + application.setWebEnvironment(true); + application.setLogStartupInfo(true); + application.run(args); + } +} \ No newline at end of file diff --git a/src/main/java/com/company/hello/api/HelloController.java b/src/main/java/com/company/hello/api/HelloController.java new file mode 100755 index 0000000..c12d2e4 --- /dev/null +++ b/src/main/java/com/company/hello/api/HelloController.java @@ -0,0 +1,14 @@ +package com.company.hello.api; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping(value = "/api", produces = "text/plain") +@RestController +public class HelloController { + + @RequestMapping("/greeting") + public String greeting() { + return "Greetings from Spring Boot!"; + } +} diff --git a/src/main/java/com/company/hello/security/HttpError.java b/src/main/java/com/company/hello/security/HttpError.java new file mode 100755 index 0000000..f1876a0 --- /dev/null +++ b/src/main/java/com/company/hello/security/HttpError.java @@ -0,0 +1,33 @@ +package com.company.hello.security; + +import com.company.hello.util.Arguments; +import com.google.common.base.Objects; + +public class HttpError { + private final int httpStatus; + + private final String errorMessage; + + public HttpError(final int httpStatus, final String errorMessage) { + Arguments.greaterThan("httpStatus", httpStatus, 0); + + this.httpStatus = httpStatus; + this.errorMessage = errorMessage; + } + + public int getHttpStatus() { + return httpStatus; + } + + public String getErrorMessage() { + return errorMessage; + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("httpStatus", httpStatus) + .add("errorMessage", errorMessage) + .toString(); + } +} diff --git a/src/main/java/com/company/hello/security/RestAuthenticationEntryPoint.java b/src/main/java/com/company/hello/security/RestAuthenticationEntryPoint.java new file mode 100755 index 0000000..71484ae --- /dev/null +++ b/src/main/java/com/company/hello/security/RestAuthenticationEntryPoint.java @@ -0,0 +1,45 @@ +package com.company.hello.security; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; + +public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { + + static final int UNAUTHORIZED = HttpStatus.UNAUTHORIZED.value(); + + private final ObjectMapper objectMapper; + + public RestAuthenticationEntryPoint(final ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + @Override + public void commence(@NotNull final HttpServletRequest request, + @NotNull final HttpServletResponse response, final AuthenticationException authException) + throws IOException, ServletException { + + String message = "No authentication exception provided. This is an unexpected error."; + if (authException != null) { + message = authException.getMessage(); + } + + HttpError error = new HttpError(UNAUTHORIZED, message); + + final String data = objectMapper.writeValueAsString(error); + + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(error.getHttpStatus()); + response.setContentLength(data.length()); + response.getWriter().print(data); + response.getWriter().flush(); + } +} diff --git a/src/main/java/com/company/hello/security/SecurityConfiguration.java b/src/main/java/com/company/hello/security/SecurityConfiguration.java new file mode 100755 index 0000000..d8eca6e --- /dev/null +++ b/src/main/java/com/company/hello/security/SecurityConfiguration.java @@ -0,0 +1,106 @@ +package com.company.hello.security; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter; + +import javax.validation.constraints.NotNull; + +@Configuration +@EnableWebMvcSecurity +public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + static final String X_AUTH_TOKEN = "X-Auth-Token"; + + static final String AUTHENTICATION_ENABLED = "authentication.enabled"; + + //@Bean + //public AuthenticationManager authenticationManager(final AgentRepository agentRepository, + // final UserRepository userRepository, + // @Value("${authentication.session.timeout.min}") final int tokenTimeoutMinutes) { + // return new TokenBasedAuthenticationManager(agentRepository, userRepository, + // tokenTimeoutMinutes); + //} + + @Bean + public AuthenticationEntryPoint authenticationEntryPoint(final ObjectMapper objectMapper) { + return new RestAuthenticationEntryPoint(objectMapper); + } + + @Bean + public RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter( + final AuthenticationManager authenticationManager) { + RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter(); + filter.setAuthenticationManager(authenticationManager); + filter.setExceptionIfHeaderMissing(false); + filter.setPrincipalRequestHeader(X_AUTH_TOKEN); + filter.setInvalidateSessionOnPrincipalChange(true); + filter.setCheckForPrincipalChanges(false); + filter.setContinueFilterChainOnUnsuccessfulAuthentication(false); + return filter; + } + + //@Bean + //public TokenRenewalFilter tokenRenewalFilter() { + // return new TokenRenewalFilter(X_AUTH_TOKEN); + //} + + //@Override + //public void configure(WebSecurity web) throws Exception { + // web.ignoring().antMatchers("/frontend/**"); + //} + + /** + * Configures the HTTP filter chain depending on configuration settings. + * + * Note that this exception is thrown in spring security headerAuthenticationFilter chain and will + * not be logged as error. Instead the ExceptionTranslationFilter will handle it and clear the + * security context. Enabling DEBUG logging for 'org.springframework.security' will help + * understanding headerAuthenticationFilter chain + */ + @Override + protected void configure(final HttpSecurity http) throws Exception { + + AuthenticationEntryPoint authenticationEntryPoint = + fromContext(http, AuthenticationEntryPoint.class); + + //@formatter:off + http + .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint) + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .anonymous() + .and() + .headers().disable() + .requestCache().disable() + .servletApi().disable() + .x509().disable() + .csrf().disable() + .httpBasic().disable() + .formLogin().disable() + .logout().disable(); + //@formatter:on + + http.authorizeRequests().antMatchers("/**").permitAll(); + } + + private T fromContext(@NotNull final HttpSecurity http, + @NotNull final Class requiredType) { + ApplicationContext ctx = context(http); + return ctx.getBean(requiredType); + } + + private ApplicationContext context(final HttpSecurity http) { + //noinspection SuspiciousMethodCalls + return (ApplicationContext) http.getSharedObjects().get(ApplicationContext.class); + } +} diff --git a/src/main/java/com/company/hello/util/Arguments.java b/src/main/java/com/company/hello/util/Arguments.java new file mode 100755 index 0000000..d75de53 --- /dev/null +++ b/src/main/java/com/company/hello/util/Arguments.java @@ -0,0 +1,42 @@ +package com.company.hello.util; + +import com.google.common.base.Strings; + +import java.util.Objects; + +public final class Arguments { + private Arguments() { + } + + public static void notNull(final String argumentName, final Object argument) { + notEmpty("argumentName", argumentName); + + if (Objects.isNull(argument)) { + throw new IllegalArgumentException( + String.format("The argument '%s' must not be null.", argumentName)); + } + } + + public static void greaterThan(final String argumentName, final long value, + final long comparator) { + notEmpty("argumentName", argumentName); + + if (value <= comparator) { + throw new IllegalArgumentException( + String.format("The value of argument '%s' has to be greater than '%d'.", argumentName, + value) + ); + } + } + + public static void notEmpty(final String argumentName, final String value) { + if (Strings.isNullOrEmpty(argumentName)) { + throw new IllegalArgumentException("The 'argumentName' must not be null or empty."); + } + + if (Strings.isNullOrEmpty(value)) { + throw new IllegalArgumentException( + String.format("The argument '%s' must not be null or empty.", argumentName)); + } + } +} diff --git a/src/main/java/com/company/hello/web/Connectors.java b/src/main/java/com/company/hello/web/Connectors.java new file mode 100755 index 0000000..804f5c8 --- /dev/null +++ b/src/main/java/com/company/hello/web/Connectors.java @@ -0,0 +1,69 @@ +package com.company.hello.web; + +import com.company.hello.util.Arguments; +import com.google.common.base.Strings; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.SecureRequestCustomizer; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +public final class Connectors { + + static final String SCHEME_HTTPS = "https"; + static final String SCHEME_HTTP = "http"; + static final String KEYSTORE_TYPE_PKCS12 = "pkcs12"; + static final String HTTP_1_1 = "http/1.1"; + public static final int IDLE_TIMEOUT = 15000; + + private Connectors() { + } + + public static ServerConnector http(final Server server, final int port) { + Arguments.notNull("server", server); + Arguments.greaterThan("port", port, 1024); + + HttpConfiguration httpConfig = new HttpConfiguration(); + httpConfig.setSecureScheme(SCHEME_HTTP); + httpConfig.setOutputBufferSize(32768); + httpConfig.setSendXPoweredBy(true); + + final ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(httpConfig)); + http.setPort(port); + http.setIdleTimeout(IDLE_TIMEOUT); + + return http; + } + + public static ServerConnector https(final Server server, final int port, final String sslCertFile, + final String sslCertPass) { + Arguments.notNull("server", server); + Arguments.greaterThan("port", port, 1024); + Arguments.notEmpty("sslCertFile", sslCertFile); + + SslContextFactory sslContextFactory = new SslContextFactory(); + sslContextFactory.setKeyStorePath(sslCertFile); + sslContextFactory.setKeyStoreType(KEYSTORE_TYPE_PKCS12); + if (!Strings.isNullOrEmpty(sslCertPass)) { + sslContextFactory.setKeyStorePassword(sslCertPass); + } + + HttpConfiguration httpConfig = new HttpConfiguration(); + httpConfig.setSecureScheme(SCHEME_HTTPS); + httpConfig.setSecurePort(port); + httpConfig.setOutputBufferSize(32768); + + HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig); + httpsConfig.addCustomizer(new SecureRequestCustomizer()); + + ServerConnector https = + new ServerConnector(server, new SslConnectionFactory(sslContextFactory, HTTP_1_1), + new HttpConnectionFactory(httpsConfig)); + https.setPort(port); + https.setIdleTimeout(IDLE_TIMEOUT); + + return https; + } +} diff --git a/src/main/java/com/company/hello/web/WebConfiguration.java b/src/main/java/com/company/hello/web/WebConfiguration.java new file mode 100755 index 0000000..0b44cc7 --- /dev/null +++ b/src/main/java/com/company/hello/web/WebConfiguration.java @@ -0,0 +1,146 @@ +package com.company.hello.web; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JSR310Module; +import com.google.common.collect.Lists; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; +import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainer; +import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +import java.nio.charset.Charset; +import java.util.List; + +@Configuration +@EnableWebMvc +public class WebConfiguration extends WebMvcConfigurerAdapter { + + private static final Logger LOG = LoggerFactory.getLogger(WebConfiguration.class); + + public static final String CONTEXT_PATH = "/dashboard"; + + public static final int NO_CACHE = 0; + + /** + * The following command may be used to create a valid .p12 self signed certificate: + * + * keytool -genkey -alias dashboard-server -storetype PKCS12 -keyalg RSA -keysize 2048 + * -keystore server.p12 -validity 3650 + * + * @return The servlet container factory for creating the embedded Jetty server with SSL enabled + */ + @Bean + public JettyEmbeddedServletContainerFactory containerFactory( + @Value("${jetty.port}") final int jettyPort, @Value("${ssl.enabled}") final boolean sslEnabled, + @Value("${ssl.cert.file}") final String sslCertFile, + @Value("${ssl.cert.pass}") final String sslCertPass) { + return new JettyEmbeddedServletContainerFactory() { + @Override + protected JettyEmbeddedServletContainer getJettyEmbeddedServletContainer( + final Server server) { + LOG.info("SSL enabled: {}", sslEnabled); + + ServerConnector connector; + if (sslEnabled) { + connector = Connectors.https(server, jettyPort, sslCertFile, sslCertPass); + } else { + connector = Connectors.http(server, jettyPort); + } + server.setConnectors(new Connector[] {connector}); + server.setStopAtShutdown(true); + + return super.getJettyEmbeddedServletContainer(server); + } + }; + } + + @Bean + public EmbeddedServletContainerCustomizer containerCustomizer( + @Value("${authentication.session.timeout.min}") final int sessionTimeout) { + return container -> { + container.setRegisterJspServlet(false); + container.setSessionTimeout(sessionTimeout); + container.setContextPath(CONTEXT_PATH); + }; + } + + @Bean + public ObjectMapper jacksonObjectMapper() { + //Hibernate4Module hibernate4Module = new Hibernate4Module(); + //hibernate4Module.enable( + // Hibernate4Module.Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS); + + ObjectMapper mapper = new ObjectMapper(); + mapper.findAndRegisterModules(); + //mapper.registerModule(hibernate4Module); + mapper.registerModule(new JSR310Module()); + mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true); + + mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT); + mapper.enable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS); + mapper.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES); + mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + + mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + mapper.disable(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS); + mapper.disable(SerializationFeature.WRITE_NULL_MAP_VALUES); + return mapper; + } + + @Bean + public MappingJackson2HttpMessageConverter jacksonConverter(ObjectMapper objectMapper) { + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); + converter.setObjectMapper(objectMapper); + converter.setSupportedMediaTypes(Lists.newArrayList(MediaType.APPLICATION_JSON)); + return converter; + } + + @Bean + public StringHttpMessageConverter stringConverter() { + return new StringHttpMessageConverter(Charset.forName("UTF-8")); + } + + @Override + public void configureMessageConverters(List> converters) { + converters.add(jacksonConverter(jacksonObjectMapper())); + converters.add(stringConverter()); + } + + @Override + public void configureAsyncSupport(final AsyncSupportConfigurer configurer) { + final ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); + taskExecutor.setCorePoolSize(10); + taskExecutor.setMaxPoolSize(30); + taskExecutor.setWaitForTasksToCompleteOnShutdown(true); + taskExecutor.initialize(); + + configurer.setTaskExecutor(taskExecutor); + } + + @Override + public void addResourceHandlers(final ResourceHandlerRegistry registry) { + registry.addResourceHandler("/**") + .addResourceLocations("classpath:/frontend/**") + .setCachePeriod(NO_CACHE); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100755 index 0000000..335a144 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,7 @@ +authentication.session.timeout.min=30 + +jetty.port=8080 + +ssl.enabled=false +ssl.cert.file= +ssl.cert.pass= \ No newline at end of file diff --git a/src/main/resources/frontend/index.html b/src/main/resources/frontend/index.html new file mode 100755 index 0000000..d7e855f --- /dev/null +++ b/src/main/resources/frontend/index.html @@ -0,0 +1,10 @@ + + + + + Dummy static resource + + +Dummy static resource. + + \ No newline at end of file diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100755 index 0000000..011448a --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,43 @@ + + + + + true + + + + + + + + ${CONSOLE_LOG_PATTERN} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/com/company/hello/api/HelloControllerTest.java b/src/test/java/com/company/hello/api/HelloControllerTest.java new file mode 100755 index 0000000..c850420 --- /dev/null +++ b/src/test/java/com/company/hello/api/HelloControllerTest.java @@ -0,0 +1,11 @@ +package com.company.hello.api; + +import com.company.hello.api.HelloController; + +public class HelloControllerTest { + + public void testGreeting() { + HelloController controller = new HelloController(); + controller.greeting(); + } +} diff --git a/src/test/java/com/company/hello/web/ConnectorsTest.java b/src/test/java/com/company/hello/web/ConnectorsTest.java new file mode 100755 index 0000000..d60e9c6 --- /dev/null +++ b/src/test/java/com/company/hello/web/ConnectorsTest.java @@ -0,0 +1,48 @@ +package com.company.hello.web; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; +import static org.mockito.Mockito.mock; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.testng.annotations.Test; + +public class ConnectorsTest { + @Test(expectedExceptions = IllegalArgumentException.class) + public void testHttpWithNullServer() { + Connectors.http(null, 1010); + failBecauseExceptionWasNotThrown(IllegalArgumentException.class); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testHttpWithInvalidPort() { + Connectors.http(mock(Server.class), 1024); + failBecauseExceptionWasNotThrown(IllegalArgumentException.class); + } + + @Test + public void testHttp() { + final ServerConnector http = Connectors.http(mock(Server.class), 1025); + assertThat(http.isStopped()).isTrue(); + http.shutdown(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testHttpsWithNullServer() { + Connectors.https(null, 1010, null, null); + failBecauseExceptionWasNotThrown(IllegalArgumentException.class); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testHttpsWithNullCertFile() { + Connectors.https(mock(Server.class), 1010, null, "ewerk"); + failBecauseExceptionWasNotThrown(IllegalArgumentException.class); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testHttpsWithEmptyCertFile() { + Connectors.https(mock(Server.class), 1010, "", "ewerk"); + failBecauseExceptionWasNotThrown(IllegalArgumentException.class); + } +}