package io.lumify.web; import com.altamiracorp.bigtable.model.ModelSession; import com.google.inject.Inject; import com.google.inject.Injector; import io.lumify.core.FrameworkUtils; import io.lumify.core.bootstrap.InjectHelper; import io.lumify.core.bootstrap.LumifyBootstrap; import io.lumify.core.config.Configuration; import io.lumify.core.config.ConfigurationLoader; import io.lumify.core.ingest.graphProperty.GraphPropertyRunner; import io.lumify.core.ingest.video.VideoFrameInfo; import io.lumify.core.model.longRunningProcess.LongRunningProcessRepository; import io.lumify.core.model.longRunningProcess.LongRunningProcessRunner; import io.lumify.core.model.ontology.OntologyRepository; import io.lumify.core.model.termMention.TermMentionRepository; import io.lumify.core.model.user.AuthorizationRepository; import io.lumify.core.model.user.UserRepository; import io.lumify.core.model.workQueue.WorkQueueRepository; import io.lumify.core.model.workspace.WorkspaceRepository; import io.lumify.core.security.LumifyVisibility; import io.lumify.core.user.User; import io.lumify.core.util.GraphUtil; import io.lumify.core.util.LumifyLogger; import io.lumify.core.util.LumifyLoggerFactory; import org.atmosphere.cache.UUIDBroadcasterCache; import org.atmosphere.cpr.AtmosphereHandler; import org.atmosphere.cpr.AtmosphereInterceptor; import org.atmosphere.cpr.AtmosphereServlet; import org.atmosphere.cpr.SessionSupport; import org.atmosphere.interceptor.HeartbeatInterceptor; import org.securegraph.Graph; import javax.servlet.*; import javax.servlet.annotation.ServletSecurity; import java.util.EnumSet; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; public class ApplicationBootstrap implements ServletContextListener { public static final String CONFIG_HTTP_TRANSPORT_GUARANTEE = "http.transportGuarantee"; private static LumifyLogger LOGGER; public static final String APP_CONFIG_LOADER = "application.config.loader"; public static final String LUMIFY_SERVLET_NAME = "lumify"; public static final String ATMOSPHERE_SERVLET_NAME = "atmosphere"; public static final String DEBUG_FILTER_NAME = "debug"; public static final String CACHE_FILTER_NAME = "cache"; private UserRepository userRepository; private boolean shouldContinueToRunLongRunningProcess; @Override public void contextInitialized(ServletContextEvent sce) { final ServletContext context = sce.getServletContext(); System.out.println("Servlet context initialized..."); if (context != null) { final Configuration config = ConfigurationLoader.load(context.getInitParameter(APP_CONFIG_LOADER), getInitParametersAsMap(context)); LOGGER = LumifyLoggerFactory.getLogger(ApplicationBootstrap.class); LOGGER.info("Running application with configuration:\n%s", config); setupInjector(context, config); verifyGraphVersion(); setupGraphAuthorizations(); setupWebApp(context, config); setupLongRunningProcessRunner(config); setupGraphPropertyWorkerRunner(config); } else { throw new RuntimeException("Failed to initialize context. Lumify is not running."); } } private void verifyGraphVersion() { Graph graph = InjectHelper.getInstance(Graph.class); GraphUtil.verifyVersion(graph); } @Override public void contextDestroyed(ServletContextEvent sce) { safeLogInfo("BEGIN: Servlet context destroyed..."); safeLogInfo("Shutdown: ModelSession"); InjectHelper.getInstance(ModelSession.class).close(); safeLogInfo("Shutdown: Graph"); InjectHelper.getInstance(Graph.class).shutdown(); safeLogInfo("Shutdown: InjectHelper"); InjectHelper.shutdown(); safeLogInfo("Shutdown: LumifyBootstrap"); LumifyBootstrap.shutdown(); safeLogInfo("END: Servlet context destroyed..."); } private void safeLogInfo(String message) { if (LOGGER != null) { LOGGER.info("%s", message); } else { System.out.println(message); } } @Inject public void setUserRepository(UserRepository userProvider) { this.userRepository = userProvider; } private void setupInjector(ServletContext context, Configuration config) { LOGGER.debug("setupInjector"); InjectHelper.inject(this, LumifyBootstrap.bootstrapModuleMaker(config), config); // Store the injector in the context for a servlet to access later context.setAttribute(Injector.class.getName(), InjectHelper.getInjector()); if (config.get(Configuration.MODEL_PROVIDER, null) != null) { FrameworkUtils.initializeFramework(InjectHelper.getInjector(), userRepository.getSystemUser()); } InjectHelper.getInstance(OntologyRepository.class); // verify we are up } private void setupGraphAuthorizations() { LOGGER.debug("setupGraphAuthorizations"); AuthorizationRepository authorizationRepository = InjectHelper.getInstance(AuthorizationRepository.class); authorizationRepository.addAuthorizationToGraph( LumifyVisibility.SUPER_USER_VISIBILITY_STRING, UserRepository.VISIBILITY_STRING, TermMentionRepository.VISIBILITY_STRING, LongRunningProcessRepository.VISIBILITY_STRING, OntologyRepository.VISIBILITY_STRING, WorkspaceRepository.VISIBILITY_STRING, VideoFrameInfo.VISIBILITY_STRING ); } private void setupWebApp(ServletContext context, Configuration config) { LOGGER.debug("setupWebApp"); Router router = new Router(context); ServletRegistration.Dynamic servlet = context.addServlet(LUMIFY_SERVLET_NAME, router); servlet.addMapping("/*"); servlet.setAsyncSupported(true); addSecurityConstraint(servlet, config); addAtmosphereServlet(context, config); addDebugFilter(context); addCacheFilter(context); LOGGER.warn("JavaScript / Less modifications will not be reflected on server. Run `grunt watch` from webapp directory in development"); } private void addAtmosphereServlet(ServletContext context, Configuration config) { ServletRegistration.Dynamic servlet = context.addServlet(ATMOSPHERE_SERVLET_NAME, AtmosphereServlet.class); context.addListener(SessionSupport.class); servlet.addMapping("/messaging/*"); servlet.setAsyncSupported(true); servlet.setLoadOnStartup(0); servlet.setInitParameter(AtmosphereHandler.class.getName(), Messaging.class.getName()); servlet.setInitParameter("org.atmosphere.cpr.sessionSupport", "true"); servlet.setInitParameter("org.atmosphere.cpr.broadcastFilterClasses", MessagingFilter.class.getName()); servlet.setInitParameter(AtmosphereInterceptor.class.getName(), HeartbeatInterceptor.class.getName()); servlet.setInitParameter("org.atmosphere.interceptor.HeartbeatInterceptor.heartbeatFrequencyInSeconds", "30"); servlet.setInitParameter("org.atmosphere.cpr.CometSupport.maxInactiveActivity", "-1"); servlet.setInitParameter("org.atmosphere.cpr.broadcasterCacheClass", UUIDBroadcasterCache.class.getName()); servlet.setInitParameter("org.atmosphere.websocket.maxTextMessageSize", "1048576"); servlet.setInitParameter("org.atmosphere.websocket.maxBinaryMessageSize", "1048576"); addSecurityConstraint(servlet, config); } private void addDebugFilter(ServletContext context) { FilterRegistration.Dynamic filter = context.addFilter(DEBUG_FILTER_NAME, RequestDebugFilter.class); filter.setAsyncSupported(true); filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*"); } private void addCacheFilter(ServletContext context) { FilterRegistration.Dynamic filter = context.addFilter(CACHE_FILTER_NAME, CacheServletFilter.class); filter.setAsyncSupported(true); String[] mappings = new String[]{"/", "*.html", "*.css", "*.js", "*.ejs", "*.less", "*.hbs", "*.map"}; for (String mapping : mappings) { filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, mapping); } } private void addSecurityConstraint(ServletRegistration.Dynamic servletRegistration, Configuration config) { ServletSecurity.TransportGuarantee transportGuarantee = ServletSecurity.TransportGuarantee.CONFIDENTIAL; String constraintType = config.get(CONFIG_HTTP_TRANSPORT_GUARANTEE, null); if (constraintType != null) { transportGuarantee = ServletSecurity.TransportGuarantee.valueOf(constraintType); } HttpConstraintElement httpConstraintElement = new HttpConstraintElement(transportGuarantee); ServletSecurityElement securityElement = new ServletSecurityElement(httpConstraintElement); servletRegistration.setServletSecurity(securityElement); } private Map<String, String> getInitParametersAsMap(ServletContext context) { Map<String, String> initParameters = new HashMap<>(); Enumeration<String> e = context.getInitParameterNames(); while (e.hasMoreElements()) { String initParameterName = e.nextElement(); initParameters.put(initParameterName, context.getInitParameter(initParameterName)); } return initParameters; } private void setupLongRunningProcessRunner(final Configuration config) { LOGGER.debug("setupLongRunningProcessRunner"); boolean enabled = Boolean.parseBoolean(config.get(Configuration.WEB_APP_EMBEDDED_LONG_RUNNING_PROCESS_RUNNER_ENABLED, Boolean.toString(Configuration.WEB_APP_EMBEDDED_LONG_RUNNING_PROCESS_RUNNER_ENABLED_DEFAULT))); if (!enabled) { LOGGER.debug("skipping embedded long running process runners"); return; } int threadCount = Integer.parseInt(config.get(Configuration.WEB_APP_EMBEDDED_LONG_RUNNING_PROCESS_RUNNER_THREAD_COUNT, Integer.toString(Configuration.WEB_APP_EMBEDDED_LONG_RUNNING_PROCESS_RUNNER_THREAD_COUNT_DEFAULT))); final LongRunningProcessRunner longRunningProcessRunner = InjectHelper.getInstance(LongRunningProcessRunner.class); longRunningProcessRunner.prepare(config.toMap()); final WorkQueueRepository workQueueRepository = InjectHelper.getInstance(WorkQueueRepository.class); LOGGER.debug("long running process runners: %d", threadCount); shouldContinueToRunLongRunningProcess = true; for (int i = 0; i < threadCount; i++) { Thread t = new Thread(new Runnable() { @Override public void run() { delayStart(); while (shouldContinueToRunLongRunningProcess) { WorkQueueRepository.LongRunningProcessMessage longRunningProcessMessage = workQueueRepository.getNextLongRunningProcessMessage(); if (longRunningProcessMessage == null) { continue; } try { longRunningProcessRunner.process(longRunningProcessMessage.getMessage()); longRunningProcessMessage.complete(); } catch (Throwable ex) { LOGGER.error("Failed to process long running process: %s", longRunningProcessMessage.getMessage()); longRunningProcessMessage.complete(ex); } } } }); t.setName("long-running-process-runner-" + t.getId()); t.setDaemon(true); LOGGER.debug("starting long running process runner thread: %s", t.getName()); t.start(); } } private void setupGraphPropertyWorkerRunner(Configuration config) { LOGGER.debug("setupGraphPropertyWorkerRunner"); boolean enabled = Boolean.parseBoolean(config.get(Configuration.WEB_APP_EMBEDDED_GRAPH_PROPERTY_WORKER_RUNNER_ENABLED, Boolean.toString(Configuration.WEB_APP_EMBEDDED_GRAPH_PROPERTY_WORKER_RUNNER_ENABLED_DEFAULT))); if (!enabled) { LOGGER.debug("skipping embedded graph property worker"); return; } int threadCount = Integer.parseInt(config.get(Configuration.WEB_APP_EMBEDDED_GRAPH_PROPERTY_WORKER_RUNNER_THREAD_COUNT, Integer.toString(Configuration.WEB_APP_EMBEDDED_GRAPH_PROPERTY_WORKER_RUNNER_THREAD_COUNT_DEFAULT))); final User user = userRepository.getSystemUser(); LOGGER.debug("starting graph property worker runners: %d", threadCount); for (int i = 0; i < threadCount; i++) { Thread t = new Thread(new Runnable() { @Override public void run() { delayStart(); GraphPropertyRunner graphPropertyRunner = InjectHelper.getInstance(GraphPropertyRunner.class); graphPropertyRunner.prepare(user); try { graphPropertyRunner.run(); } catch (Exception ex) { LOGGER.error("Failed running graph property runner", ex); } } }); t.setName("graph-property-worker-runner-" + t.getId()); t.setDaemon(true); LOGGER.debug("starting graph property worker runner thread: %s", t.getName()); t.start(); } } /** * Delay the start of GPW and long running processes so the web app comes up faster */ private void delayStart() { try { Thread.sleep(3000); } catch (InterruptedException e) { LOGGER.error("Could not sleep", e); } } }