/* * Copyright 2013-2017 Erudika. https://erudika.com * * 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. * * For issues and patches go to: https://github.com/erudika */ package com.erudika.para; import ch.qos.logback.access.jetty.RequestLogImpl; import com.erudika.para.aop.AOPModule; import com.erudika.para.cache.CacheModule; import com.erudika.para.core.utils.CoreUtils; import com.erudika.para.email.EmailModule; import com.erudika.para.i18n.I18nModule; import com.erudika.para.iot.IoTModule; import com.erudika.para.persistence.PersistenceModule; import com.erudika.para.queue.QueueModule; import com.erudika.para.rest.Api1; import com.erudika.para.search.SearchModule; import com.erudika.para.security.SecurityModule; import com.erudika.para.storage.StorageModule; import com.erudika.para.utils.Config; import com.erudika.para.utils.filters.CORSFilter; import com.erudika.para.utils.filters.ErrorFilter; import com.erudika.para.utils.filters.GZipServletFilter; import com.google.inject.Module; import javax.annotation.PreDestroy; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletException; import org.apache.commons.lang3.math.NumberUtils; import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.ForwardedRequestCustomizer; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.server.handler.RequestLogHandler; import org.glassfish.jersey.servlet.ServletContainer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.Banner; import org.springframework.boot.SpringApplication; import org.springframework.boot.builder.ParentContextApplicationContextInitializer; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.jetty.JettyServerCustomizer; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.boot.web.support.ServletContextApplicationContextInitializer; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.web.WebApplicationInitializer; import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.WebApplicationContext; /** * Para modules are initialized and destroyed from here. * * @author Alex Bogdanovski [alex@erudika.com] */ @Configuration @ComponentScan public class ParaServer implements WebApplicationInitializer, Ordered { private static final Logger logger = LoggerFactory.getLogger(ParaServer.class); /** * Returns the list of core modules. * @return the core modules */ public static Module[] getCoreModules() { return new Module[] { new PersistenceModule(), new SearchModule(), new CacheModule(), new AOPModule(), new IoTModule(), new EmailModule(), new I18nModule(), new QueueModule(), new StorageModule(), new SecurityModule() }; } @Override public int getOrder() { return 1; } /** * @return API servlet bean */ @Bean public ServletRegistrationBean apiV1RegistrationBean() { String path = Api1.PATH + "*"; ServletRegistrationBean reg = new ServletRegistrationBean(new ServletContainer(new Api1()), path); logger.debug("Initializing Para API v1 [{}]...", path); reg.setName(Api1.class.getSimpleName()); reg.setAsyncSupported(true); reg.setEnabled(true); reg.setOrder(3); return reg; } /** * @return GZIP filter bean */ @Bean public FilterRegistrationBean gzipFilterRegistrationBean() { String path = Api1.PATH + "*"; FilterRegistrationBean frb = new FilterRegistrationBean(new GZipServletFilter()); logger.debug("Initializing GZip filter [{}]...", path); frb.addUrlPatterns(path); frb.setAsyncSupported(true); frb.setEnabled(Config.GZIP_ENABLED); frb.setMatchAfter(true); frb.setOrder(20); return frb; } /** * @return CORS filter bean */ @Bean public FilterRegistrationBean corsFilterRegistrationBean() { String path = Api1.PATH + "*"; logger.debug("Initializing CORS filter [{}]...", path); FilterRegistrationBean frb = new FilterRegistrationBean(new CORSFilter()); frb.addInitParameter("cors.support.credentials", "true"); frb.addInitParameter("cors.allowed.methods", "GET,POST,PATCH,PUT,DELETE,HEAD,OPTIONS"); frb.addInitParameter("cors.exposed.headers", "Cache-Control,Content-Length,Content-Type,Date,ETag,Expires"); frb.addInitParameter("cors.allowed.headers", "Origin,Accept,X-Requested-With,Content-Type," + "Access-Control-Request-Method,Access-Control-Request-Headers,X-Amz-Credential," + "X-Amz-Date,Authorization"); frb.addUrlPatterns(path); frb.setAsyncSupported(true); frb.setEnabled(Config.CORS_ENABLED); frb.setMatchAfter(false); frb.setOrder(2); return frb; } /** * @return Jetty config bean */ @Bean public EmbeddedServletContainerFactory jettyConfigBean() { JettyEmbeddedServletContainerFactory jef = new JettyEmbeddedServletContainerFactory(); jef.addServerCustomizers(new JettyServerCustomizer() { public void customize(Server server) { if (Config.getConfigBoolean("access_log_enabled", true)) { // enable access log via Logback HandlerCollection handlers = new HandlerCollection(); for (Handler handler : server.getHandlers()) { handlers.addHandler(handler); } RequestLogHandler reqLogs = new RequestLogHandler(); reqLogs.setServer(server); RequestLogImpl rli = new RequestLogImpl(); rli.setResource("/logback-access.xml"); rli.setQuiet(true); rli.start(); reqLogs.setRequestLog(rli); handlers.addHandler(reqLogs); server.setHandler(handlers); } for (Connector y : server.getConnectors()) { for (ConnectionFactory cf : y.getConnectionFactories()) { if (cf instanceof HttpConnectionFactory) { HttpConnectionFactory dcf = (HttpConnectionFactory) cf; // support for X-Forwarded-Proto // redirect back to https if original request uses it if (Config.IN_PRODUCTION) { HttpConfiguration httpConfiguration = dcf.getHttpConfiguration(); httpConfiguration.addCustomizer(new ForwardedRequestCustomizer()); } // Disable Jetty version header dcf.getHttpConfiguration().setSendServerVersion(false); } } } } }); int defaultPort = NumberUtils.toInt(System.getProperty("jetty.http.port", "8080")); jef.setPort(NumberUtils.toInt(System.getProperty("server.port"), defaultPort)); logger.info("Listening on port {}...", jef.getPort()); return jef; } /** * Called before shutdown. */ @PreDestroy public void preDestroy() { Para.destroy(); } @Override public void onStartup(ServletContext sc) throws ServletException { runAsWAR(sc, ParaServer.class); } /** * This is the initializing method when running ParaServer as WAR, * deployed to a servlet container. * @param sc the ServletContext instance * @param sources the application classes that will be scanned * @return the application context */ protected static WebApplicationContext runAsWAR(ServletContext sc, Object... sources) { ApplicationContext parent = null; Object object = sc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); if (object instanceof ApplicationContext) { logger.info("Root context already created (using as parent)."); parent = (ApplicationContext) object; sc.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null); } SpringApplicationBuilder application = new SpringApplicationBuilder(sources); if (parent != null) { application.initializers(new ParentContextApplicationContextInitializer(parent)); } application.initializers(new ServletContextApplicationContextInitializer(sc)); application.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class); // entry point (WAR) application.profiles(Config.ENVIRONMENT); application.web(true); application.bannerMode(Banner.Mode.OFF); Para.addInitListener(CoreUtils.getInstance()); Para.initialize(getCoreModules()); // Ensure error pages are registered application.sources(ErrorFilter.class); WebApplicationContext rootAppContext = (WebApplicationContext) application.run(); if (rootAppContext != null) { sc.addListener(new ContextLoaderListener(rootAppContext) { @Override public void contextInitialized(ServletContextEvent event) { // no-op because the application context is already initialized } }); sc.getSessionCookieConfig().setName("sess"); sc.getSessionCookieConfig().setMaxAge(1); sc.getSessionCookieConfig().setHttpOnly(true); sc.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false"); } return rootAppContext; } /** * This is the initializing method when running ParaServer as executable JAR (or WAR), * from the command line: java -jar para.jar. * @param args command line arguments array (same as those in {@code void main(String[] args)} ) * @param sources the application classes that will be scanned */ protected static void runAsJAR(String[] args, Object... sources) { // entry point (JAR) SpringApplication app = new SpringApplication(sources); app.setAdditionalProfiles(Config.ENVIRONMENT); app.setWebEnvironment(true); app.setBannerMode(Banner.Mode.OFF); Para.addInitListener(CoreUtils.getInstance()); Para.initialize(getCoreModules()); app.run(args); } /** * Para starts from here. * @param args args */ public static void main(String[] args) { runAsJAR(args, ParaServer.class); } }