// Copyright © 2015 HSL <https://www.hsl.fi> // This program is dual-licensed under the EUPL v1.2 and AGPLv3 licenses. package fi.hsl.parkandride; import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.nitorcreations.willow.logging.tomcat8.WillowAccessValve; import fi.hsl.parkandride.core.domain.Phone; import fi.hsl.parkandride.core.domain.Time; import fi.hsl.parkandride.core.service.BatchingRequestLogService; import fi.hsl.parkandride.front.*; import fi.hsl.parkandride.front.geojson.GeojsonDeserializer; import fi.hsl.parkandride.front.geojson.GeojsonSerializer; import org.geolatte.common.Feature; import org.geolatte.common.dataformats.json.jackson.JsonMapper; import org.geolatte.geom.Geometry; import org.geolatte.geom.Point; import org.geolatte.geom.Polygon; import org.h2.server.web.WebServlet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.actuate.system.ApplicationPidFileWriter; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.web.HttpMessageConverters; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; import org.springframework.boot.context.embedded.*; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Profile; import org.springframework.http.MediaType; import org.springframework.web.filter.CharacterEncodingFilter; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.config.annotation.*; import org.springframework.web.servlet.mvc.WebContentInterceptor; import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver; import java.util.List; import java.util.Properties; import static fi.hsl.parkandride.front.UrlSchema.GEOJSON; import static org.springframework.http.MediaType.APPLICATION_JSON; @SpringBootApplication @Import(Application.UiConfig.class) public class Application extends WebMvcConfigurerAdapter { private static final Logger log = LoggerFactory.getLogger(Application.class); public static void main(String[] args) { SpringApplication app = new SpringApplication(Application.class); app.addListeners(new ApplicationPidFileWriter()); app.run(args); } @Configuration @Import({WebMvcAutoConfiguration.class, DevUIConfig.class}) public static class UiConfig extends WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter implements EmbeddedServletContainerCustomizer { @Autowired private HttpMessageConverters messageConverters; @Autowired private BatchingRequestLogService batchingRequestLogService; @Bean public Module facilityModule() { return new SimpleModule("geometryModule") {{ final JsonMapper jsonMapper = new JsonMapper(); addSerializer(Geometry.class, new GeojsonSerializer<>(jsonMapper)); addDeserializer(Geometry.class, new GeojsonDeserializer<>(jsonMapper, Geometry.class)); addSerializer(Polygon.class, new GeojsonSerializer<>(jsonMapper)); addDeserializer(Polygon.class, new GeojsonDeserializer<>(jsonMapper, Polygon.class)); addSerializer(Point.class, new GeojsonSerializer<>(jsonMapper)); addDeserializer(Point.class, new GeojsonDeserializer<>(jsonMapper, Point.class)); addSerializer(Feature.class, new GeojsonSerializer<>(jsonMapper)); addSerializer(Phone.class, new PhoneSerializer()); addSerializer(Time.class, new ToStringSerializer()); }}; } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new RequestLoggingInterceptor(batchingRequestLogService)); } @Bean public CharacterEncodingFilter characterEncodingFilter() { CharacterEncodingFilter filter = new CharacterEncodingFilter(); filter.setEncoding("UTF-8"); filter.setForceEncoding(true); return filter; } @Bean public Features features() { return new Features(); } @Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { configurer.defaultContentType(APPLICATION_JSON); configurer.mediaType("json", APPLICATION_JSON); configurer.mediaType("geojson", MediaType.valueOf(GEOJSON)); } @Bean public ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver() { ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver(); resolver.setWarnLogCategory("parkandride"); resolver.setMessageConverters(messageConverters.getConverters()); return resolver; } @Override public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) { exceptionResolvers.add(exceptionHandlerExceptionResolver()); } @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { argumentResolvers.add(userArgumentResolver()); } @Bean public UserArgumentResolver userArgumentResolver() { return new UserArgumentResolver(); } @Override public void customize(ConfigurableEmbeddedServletContainer container) { MimeMappings mappings = new MimeMappings(MimeMappings.DEFAULT); mappings.add("html", "text/html;charset=UTF-8"); mappings.add("json", "application/json;charset=UTF-8"); container.setMimeMappings(mappings); if (System.getProperty("wslogging.url") != null && container instanceof TomcatEmbeddedServletContainerFactory) { TomcatEmbeddedServletContainerFactory tomcat = (TomcatEmbeddedServletContainerFactory)container; WillowAccessValve valve = new WillowAccessValve(); valve.setUrl(System.getProperty("wslogging.url")); tomcat.addContextValves(valve); } } } @Configuration @EnableWebMvc @Profile({FeatureProfile.DEV}) public static class DevUIConfig extends WebMvcConfigurerAdapter { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { String projectDir = System.getProperty("user.dir"); if (!projectDir.endsWith("application")) { projectDir += "/application"; } registry.addResourceHandler("/**").addResourceLocations( "file://" + projectDir + "/src/main/frontend/build/", "/", "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/" ); } @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("forward:/index.html"); } @Bean @Profile({ FeatureProfile.H2 }) public ServletRegistrationBean h2servletRegistration() { ServletRegistrationBean reg = new ServletRegistrationBean(new WebServlet()); reg.addUrlMappings("/console/*"); return reg; } } @Bean public FilterRegistrationBean registerMdcFilter(MDCFilter filter) { FilterRegistrationBean b = new FilterRegistrationBean(filter); b.setMatchAfter(true); return b; } @Bean public FilterRegistrationBean registerCORSFilter() { FilterRegistrationBean b = new FilterRegistrationBean(new CORSFilter()); b.setMatchAfter(true); b.setUrlPatterns(UrlSchema.CORS_ENABLED_PATHS); return b; } @Bean public FilterRegistrationBean registerApplicationVersionFilter(@Value("${app.version}") String version) { log.info("Application version is {}", version); FilterRegistrationBean b = new FilterRegistrationBean(new ApplicationVersionFilter(version)); b.setMatchAfter(true); return b; } @Override public void addInterceptors(InterceptorRegistry registry) { WebContentInterceptor webContentInterceptor = new WebContentInterceptor(); Properties cacheMappings = new Properties(); cacheMappings.setProperty(UrlSchema.API + "/**", "0"); webContentInterceptor.setCacheMappings(cacheMappings); registry.addInterceptor(webContentInterceptor); } }