package org.libresonic.player.boot; import net.sf.ehcache.constructs.web.ShutdownListener; import org.directwebremoting.servlet.DwrServlet; import org.libresonic.player.Logger; import org.libresonic.player.filter.*; import org.libresonic.player.spring.LibresonicPropertySourceConfigurer; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; import org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.boot.web.support.SpringBootServletInitializer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportResource; import org.springframework.util.ReflectionUtils; import javax.servlet.Filter; import javax.servlet.ServletContextListener; import java.lang.reflect.Method; @SpringBootApplication(exclude = { JmxAutoConfiguration.class, JdbcTemplateAutoConfiguration.class, DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, MultipartAutoConfiguration.class, // TODO: update to use spring boot builtin multipart support LiquibaseAutoConfiguration.class}) @Configuration @ImportResource(value = {"classpath:/applicationContext-service.xml", "classpath:/applicationContext-cache.xml", "classpath:/applicationContext-sonos.xml", "classpath:/libresonic-servlet.xml"}) public class Application extends SpringBootServletInitializer implements EmbeddedServletContainerCustomizer { private static final Logger LOG = Logger.getLogger(Application.class); /** * Registers the DWR servlet. * * @return a registration bean. */ @Bean public ServletRegistrationBean dwrServletRegistrationBean() { ServletRegistrationBean servlet = new ServletRegistrationBean(new DwrServlet(), "/dwr/*"); servlet.addInitParameter("crossDomainSessionSecurity","false"); return servlet; } @Bean public ServletRegistrationBean cxfServletBean() { return new ServletRegistrationBean(new org.apache.cxf.transport.servlet.CXFServlet(), "/ws/*"); } @Bean public ServletContextListener ehCacheShutdownListener() { return new ShutdownListener(); } @Bean public FilterRegistrationBean bootstrapVerificationFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(bootstrapVerificationFiler()); registration.addUrlPatterns("/*"); registration.setName("BootstrapVerificationFilter"); registration.setOrder(1); return registration; } @Bean public Filter bootstrapVerificationFiler() { return new BootstrapVerificationFilter(); } @Bean public FilterRegistrationBean parameterDecodingFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(parameterDecodingFilter()); registration.addUrlPatterns("/*"); registration.setName("ParameterDecodingFilter"); registration.setOrder(2); return registration; } @Bean public Filter parameterDecodingFilter() { return new ParameterDecodingFilter(); } @Bean public FilterRegistrationBean restFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(restFilter()); registration.addUrlPatterns("/rest/*"); registration.setName("RESTFilter"); registration.setOrder(3); return registration; } @Bean public Filter restFilter() { return new RESTFilter(); } @Bean public FilterRegistrationBean requestEncodingFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(requestEncodingFilter()); registration.addUrlPatterns("/*"); registration.addInitParameter("encoding", "UTF-8"); registration.setName("RequestEncodingFilter"); registration.setOrder(4); return registration; } @Bean public Filter requestEncodingFilter() { return new RequestEncodingFilter(); } @Bean public FilterRegistrationBean cacheFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(cacheFilter()); registration.addUrlPatterns("/icons/*", "/style/*", "/script/*", "/dwr/*", "/icons/*", "/coverArt.view", "/avatar.view"); registration.addInitParameter("Cache-Control", "max-age=36000"); registration.setName("CacheFilter"); registration.setOrder(5); return registration; } @Bean public Filter cacheFilter() { return new ResponseHeaderFilter(); } @Bean public FilterRegistrationBean noCacheFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(noCacheFilter()); registration.addUrlPatterns("/statusChart.view", "/userChart.view", "/playQueue.view", "/podcastChannels.view", "/podcastChannel.view", "/help.view", "/top.view", "/home.view"); registration.addInitParameter("Cache-Control", "no-cache, post-check=0, pre-check=0"); registration.addInitParameter("Pragma", "no-cache"); registration.addInitParameter("Expires", "Thu, 01 Dec 1994 16:00:00 GMT"); registration.setName("NoCacheFilter"); registration.setOrder(6); return registration; } @Bean public Filter metricsFilter() { return new MetricsFilter(); } @Bean public FilterRegistrationBean metricsFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(metricsFilter()); registration.setOrder(7); return registration; } @Bean public Filter noCacheFilter() { return new ResponseHeaderFilter(); } private static SpringApplicationBuilder doConfigure(SpringApplicationBuilder application) { // Customize the application or call application.sources(...) to add sources // Since our example is itself a @Configuration class (via @SpringBootApplication) // we actually don't need to override this method. return application.sources(Application.class).web(true).initializers(new LibresonicPropertySourceConfigurer()); } @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return doConfigure(application); } @Override public void customize(ConfigurableEmbeddedServletContainer container) { // Yes, there is a good reason we do this. // We cannot count on the tomcat classes being on the classpath which will // happen if the war is deployed to another app server like Jetty. So, we // ensure this class does not have any direct dependencies on any Tomcat // specific classes. try { Class<?> tomcatESCF = Class.forName("org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory"); if(tomcatESCF.isInstance(container)) { LOG.debug("Attempting to optimize tomcat"); Object tomcatESCFInstance = tomcatESCF.cast(container); Class<?> tomcatApplicationClass = Class.forName("org.libresonic.player.boot.TomcatApplication"); Method configure = ReflectionUtils.findMethod(tomcatApplicationClass, "configure", tomcatESCF); configure.invoke(null, tomcatESCFInstance); LOG.debug("Tomcat optimizations complete"); } else { LOG.debug("Skipping tomcat optimization as we are not running on tomcat"); } } catch (NoClassDefFoundError | ClassNotFoundException e) { LOG.debug("Skipping tomcat optimization as the tomcat classes are not available"); } catch (Exception e) { LOG.warn("An error happened while trying to optimize tomcat", e); } } public static void main(String[] args) { SpringApplicationBuilder builder = new SpringApplicationBuilder(); doConfigure(builder).run(args); } }