Java tutorial
/* * RESTHeart - the Web API for MongoDB * Copyright (C) SoftInstigate Srl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.restheart; import org.restheart.utils.RHDaemon; import com.mongodb.MongoClient; import static com.sun.akuma.CLibrary.LIBC; import static org.restheart.Configuration.RESTHEART_VERSION; import org.restheart.db.MongoDBClientSingleton; import org.restheart.handlers.ErrorHandler; import org.restheart.handlers.GzipEncodingHandler; import org.restheart.handlers.PipedHttpHandler; import org.restheart.handlers.RequestDispacherHandler; import org.restheart.handlers.injectors.RequestContextInjectorHandler; import org.restheart.handlers.injectors.CollectionPropsInjectorHandler; import org.restheart.handlers.injectors.DbPropsInjectorHandler; import org.restheart.handlers.injectors.LocalCachesSingleton; import org.restheart.security.AccessManager; import org.restheart.utils.ResourcesExtractor; import org.restheart.utils.LoggingInitializer; import org.restheart.handlers.RequestContext; import org.restheart.handlers.applicationlogic.ApplicationLogicHandler; import org.restheart.handlers.OptionsHandler; import org.restheart.handlers.PipedWrappingHandler; import org.restheart.handlers.injectors.BodyInjectorHandler; import org.restheart.security.handlers.SecurityHandlerDispacher; import org.restheart.security.handlers.CORSHandler; import org.restheart.utils.FileUtils; import org.restheart.utils.OSChecker; import io.undertow.Undertow; import io.undertow.security.idm.IdentityManager; import io.undertow.server.handlers.HttpContinueAcceptingHandler; import io.undertow.server.handlers.resource.FileResourceManager; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import static io.undertow.Handlers.resource; import io.undertow.Undertow.Builder; import io.undertow.server.handlers.AllowedMethodsHandler; import io.undertow.server.handlers.BlockingHandler; import io.undertow.server.handlers.GracefulShutdownHandler; import io.undertow.server.handlers.PathHandler; import io.undertow.server.handlers.RequestLimit; import io.undertow.server.handlers.RequestLimitingHandler; import io.undertow.server.handlers.resource.ResourceHandler; import io.undertow.util.HttpString; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.Map; import static org.fusesource.jansi.Ansi.Color.GREEN; import static org.fusesource.jansi.Ansi.Color.RED; import org.restheart.security.FullAccessManager; import org.restheart.security.handlers.AuthTokenHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.restheart.handlers.RequestLoggerHandler; import java.nio.file.Paths; import static io.undertow.Handlers.path; import static org.fusesource.jansi.Ansi.ansi; import org.restheart.handlers.injectors.AccountInjectorHandler; /** * * @author Andrea Di Cesare {@literal <andrea@softinstigate.com>} */ public final class Bootstrapper { private static final Logger LOGGER = LoggerFactory.getLogger(Bootstrapper.class); private static final Map<String, File> TMP_EXTRACTED_FILES = new HashMap<>(); private static Path CONF_FILE_PATH; private static GracefulShutdownHandler shutdownHandler = null; private static Configuration configuration; private static Undertow undertowServer; private Bootstrapper() { } /** * main method * * @param args command line arguments */ public static void main(final String[] args) { CONF_FILE_PATH = FileUtils.getConfigurationFilePath(args); try { // read configuration silently, to avoid logging before initializing the logger configuration = FileUtils.getConfiguration(args, true); } catch (ConfigurationException ex) { LOGGER.info("Starting " + ansi().fg(RED).bold().a("RESTHeart").reset().toString() + " instance " + ansi().fg(RED).bold().a("undefined").reset().toString()); if (RESTHEART_VERSION != null) { LOGGER.info("version {}", RESTHEART_VERSION); } logErrorAndExit(ex.getMessage() + ", exiting...", ex, false, -1); } if (!hasForkOption(args)) { initLogging(args, null); startServer(false); } else { if (OSChecker.isWindows()) { String instanceName = configuration == null ? "undefined" : configuration.getInstanceName() == null ? "undefined" : configuration.getInstanceName(); LOGGER.info("Starting " + ansi().fg(RED).bold().a("RESTHeart").reset().toString() + " instance " + ansi().fg(RED).bold().a(instanceName).reset().toString()); if (RESTHEART_VERSION != null) { LOGGER.info("version {}", RESTHEART_VERSION); } LOGGER.error("Fork is not supported on Windows"); LOGGER.info(ansi().fg(GREEN).bold().a("RESTHeart stopped").reset().toString()); System.exit(-1); } // RHDaemon only works on POSIX OSes final boolean isPosix = FileSystems.getDefault().supportedFileAttributeViews().contains("posix"); if (!isPosix) { logErrorAndExit("Unable to fork process, this is only supported on POSIX compliant OSes", null, false, -1); } RHDaemon d = new RHDaemon(); if (d.isDaemonized()) { try { d.init(); LOGGER.info("Forked process: {}", LIBC.getpid()); initLogging(args, d); } catch (Throwable t) { logErrorAndExit("Error staring forked process", t, false, false, -1); } startServer(true); } else { initLogging(args, d); try { String instanceName = configuration == null ? "undefined" : configuration.getInstanceName() == null ? "undefined" : configuration.getInstanceName(); LOGGER.info("Starting " + ansi().fg(RED).bold().a("RESTHeart").reset().toString() + " instance " + ansi().fg(RED).bold().a(instanceName).reset().toString()); if (RESTHEART_VERSION != null) { LOGGER.info("version {}", RESTHEART_VERSION); } logLoggingConfiguration(true); d.daemonize(); } catch (Throwable t) { logErrorAndExit("Error forking", t, false, false, -1); } } } } /** * logs warning message if pid file exists * * @param confFilePath * @return true if pid file exists */ private static boolean checkPidFile(Path confFilePath) { if (OSChecker.isWindows()) { return false; } // pid file name include the hash of the configuration file so that // for each configuration we can have just one instance running Path pidFilePath = FileUtils.getPidFilePath(FileUtils.getFileAbsoultePathHash(confFilePath)); if (Files.exists(pidFilePath)) { LOGGER.warn("Found pid file! If this instance is already " + "running, startup will fail with a BindException"); return true; } return false; } /** * Startup the RESTHeart server * * @param confFilePath the path of the configuration file */ public static void startup(final String confFilePath) { startup(FileUtils.getFileAbsoultePath(confFilePath)); } /** * Startup the RESTHeart server * * @param confFilePath the path of the configuration file */ public static void startup(final Path confFilePath) { try { configuration = FileUtils.getConfiguration(confFilePath, false); } catch (ConfigurationException ex) { if (RESTHEART_VERSION != null) { LOGGER.info(ansi().fg(RED).bold().a("RESTHeart").reset().toString() + " version {}", RESTHEART_VERSION); } logErrorAndExit(ex.getMessage() + ", exiting...", ex, false, -1); } startServer(false); } /** * Shutdown the RESTHeart server */ public static void shutdown() { stopServer(false); } /** * initLogging * * @param args * @param d */ private static void initLogging(final String[] args, final RHDaemon d) { LoggingInitializer.setLogLevel(configuration.getLogLevel()); if (d != null && d.isDaemonized()) { LoggingInitializer.stopConsoleLogging(); LoggingInitializer.startFileLogging(configuration.getLogFilePath()); } else if (!hasForkOption(args)) { if (!configuration.isLogToConsole()) { LoggingInitializer.stopConsoleLogging(); } if (configuration.isLogToFile()) { LoggingInitializer.startFileLogging(configuration.getLogFilePath()); } } } /** * logLoggingConfiguration * * @param fork */ private static void logLoggingConfiguration(boolean fork) { if (configuration.isLogToFile()) { LOGGER.info("Logging to file {} with level {}", configuration.getLogFilePath(), configuration.getLogLevel()); } if (!fork) { if (!configuration.isLogToConsole()) { LOGGER.info("Stop logging to console "); } else { LOGGER.info("Logging to console with level {}", configuration.getLogLevel()); } } } /** * hasForkOption * * @param args * @return true if has fork option */ private static boolean hasForkOption(final String[] args) { if (args == null || args.length < 1) { return false; } for (String arg : args) { if (arg.equals("--fork")) { return true; } } return false; } /** * startServer * * @param fork */ private static void startServer(boolean fork) { String instanceName = configuration == null ? "undefined" : configuration.getInstanceName() == null ? "undefined" : configuration.getInstanceName(); LOGGER.info("Starting " + ansi().fg(RED).bold().a("RESTHeart").reset().toString() + " instance " + ansi().fg(RED).bold().a(instanceName).reset().toString()); if (RESTHEART_VERSION != null) { LOGGER.info("version {}", RESTHEART_VERSION); } Path pidFilePath = FileUtils.getPidFilePath(FileUtils.getFileAbsoultePathHash(CONF_FILE_PATH)); boolean pidFileAlreadyExists = false; if (!OSChecker.isWindows() && pidFilePath != null) { pidFileAlreadyExists = checkPidFile(CONF_FILE_PATH); } logLoggingConfiguration(fork); LOGGER.debug("Initializing MongoDB connection pool to {} with options {}", configuration.getMongoUri().getHosts(), configuration.getMongoUri().getOptions()); try { MongoDBClientSingleton.init(configuration); //force setup MongoDBClientSingleton.getInstance(); LOGGER.info("MongoDB connection pool initialized"); LOGGER.info("MongoDB version {}", MongoDBClientSingleton.getServerVersion()); } catch (Throwable t) { logErrorAndExit("Error connecting to MongoDB. exiting..", t, false, !pidFileAlreadyExists, -1); } try { startCoreSystem(); } catch (Throwable t) { logErrorAndExit("Error starting RESTHeart. Exiting...", t, false, !pidFileAlreadyExists, -2); } Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { stopServer(false); } }); // create pid file on supported OSes if (!OSChecker.isWindows() && pidFilePath != null) { FileUtils.createPidFile(pidFilePath); } // log pid file path on supported OSes if (!OSChecker.isWindows() && pidFilePath != null) { LOGGER.info("Pid file {}", pidFilePath); } LOGGER.info(ansi().fg(GREEN).bold().a("RESTHeart started").reset().toString()); } /** * stopServer * * @param silent */ private static void stopServer(boolean silent) { stopServer(silent, true); } /** * stopServer * * @param silent * @param removePid */ private static void stopServer(boolean silent, boolean removePid) { if (!silent) { LOGGER.info("Stopping RESTHeart..."); } if (shutdownHandler != null) { if (!silent) { LOGGER.info("Waiting for pending request to complete (up to 1 minute)..."); } try { shutdownHandler.shutdown(); shutdownHandler.awaitShutdown(60 * 1000); // up to 1 minute } catch (InterruptedException ie) { LOGGER.error("Error while waiting for pending request to complete", ie); } } if (MongoDBClientSingleton.isInitialized()) { MongoClient client = MongoDBClientSingleton.getInstance().getClient(); if (!silent) { LOGGER.info("Closing MongoDB client connections..."); } try { client.close(); } catch (Throwable t) { LOGGER.warn("Error closing the MongoDB client connection", t); } } Path pidFilePath = FileUtils.getPidFilePath(FileUtils.getFileAbsoultePathHash(CONF_FILE_PATH)); if (removePid && pidFilePath != null) { if (!silent) { LOGGER.info("Removing the pid file {}", pidFilePath.toString()); } try { Files.deleteIfExists(pidFilePath); } catch (IOException ex) { LOGGER.error("Can't delete pid file {}", pidFilePath.toString(), ex); } } if (!silent) { LOGGER.info("Cleaning up temporary directories..."); } TMP_EXTRACTED_FILES.keySet().forEach(k -> { try { ResourcesExtractor.deleteTempDir(k, TMP_EXTRACTED_FILES.get(k)); } catch (URISyntaxException | IOException ex) { LOGGER.error("Error cleaning up temporary directory {}", TMP_EXTRACTED_FILES.get(k).toString(), ex); } }); undertowServer.stop(); if (!silent) { LOGGER.info(ansi().fg(GREEN).bold().a("RESTHeart stopped").reset().toString()); } LoggingInitializer.stopLogging(); } /** * startCoreSystem */ private static void startCoreSystem() { if (configuration == null) { logErrorAndExit("No configuration found. exiting..", null, false, -1); } if (!configuration.isHttpsListener() && !configuration.isHttpListener() && !configuration.isAjpListener()) { logErrorAndExit("No listener specified. exiting..", null, false, -1); } final IdentityManager identityManager = loadIdentityManager(); final AccessManager accessManager = loadAccessManager(); if (configuration.isAuthTokenEnabled()) { LOGGER.info("Token based authentication enabled with token TTL {} minutes", configuration.getAuthTokenTtl()); } SSLContext sslContext = null; try { KeyManagerFactory kmf; KeyStore ks; if (getConfiguration().isUseEmbeddedKeystore()) { char[] storepass = "restheart".toCharArray(); char[] keypass = "restheart".toCharArray(); String storename = "rakeystore.jks"; sslContext = SSLContext.getInstance("TLS"); kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); ks = KeyStore.getInstance("JKS"); ks.load(Bootstrapper.class.getClassLoader().getResourceAsStream(storename), storepass); kmf.init(ks, keypass); sslContext.init(kmf.getKeyManagers(), null, null); } else { sslContext = SSLContext.getInstance("TLS"); kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); ks = KeyStore.getInstance("JKS"); try (FileInputStream fis = new FileInputStream(new File(configuration.getKeystoreFile()))) { ks.load(fis, configuration.getKeystorePassword().toCharArray()); kmf.init(ks, configuration.getCertPassword().toCharArray()); sslContext.init(kmf.getKeyManagers(), null, null); } } } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException | CertificateException | UnrecoverableKeyException ex) { logErrorAndExit("Couldn't start RESTHeart, error with specified keystore. exiting..", ex, false, -1); } catch (FileNotFoundException ex) { logErrorAndExit("Couldn't start RESTHeart, keystore file not found. exiting..", ex, false, -1); } catch (IOException ex) { logErrorAndExit("Couldn't start RESTHeart, error reading the keystore file. exiting..", ex, false, -1); } Builder builder = Undertow.builder(); if (configuration.isHttpsListener()) { builder.addHttpsListener(configuration.getHttpsPort(), configuration.getHttpHost(), sslContext); LOGGER.info("HTTPS listener bound at {}:{}", configuration.getHttpsHost(), configuration.getHttpsPort()); } if (configuration.isHttpListener()) { builder.addHttpListener(configuration.getHttpPort(), configuration.getHttpsHost()); LOGGER.info("HTTP listener bound at {}:{}", configuration.getHttpHost(), configuration.getHttpPort()); } if (configuration.isAjpListener()) { builder.addAjpListener(configuration.getAjpPort(), configuration.getAjpHost()); LOGGER.info("Ajp listener bound at {}:{}", configuration.getAjpHost(), configuration.getAjpPort()); } LocalCachesSingleton.init(configuration); if (configuration.isLocalCacheEnabled()) { LOGGER.info("Local cache for db and collection properties enabled with TTL {} msecs", configuration.getLocalCacheTtl() < 0 ? "" : configuration.getLocalCacheTtl()); } else { LOGGER.info("Local cache for db and collection properties not enabled"); } if (configuration.isSchemaCacheEnabled()) { LOGGER.info("Local cache for schema stores enabled with TTL {} msecs", configuration.getSchemaCacheTtl() < 0 ? "" : configuration.getSchemaCacheTtl()); } else { LOGGER.info("Local cache for schema stores not enabled"); } shutdownHandler = getHandlersPipe(identityManager, accessManager); builder = builder.setIoThreads(configuration.getIoThreads()) .setWorkerThreads(configuration.getWorkerThreads()) .setDirectBuffers(configuration.isDirectBuffers()).setBufferSize(configuration.getBufferSize()) .setBuffersPerRegion(configuration.getBuffersPerRegion()).setHandler(shutdownHandler); ConfigurationHelper.setConnectionOptions(builder, configuration); undertowServer = builder.build(); undertowServer.start(); } /** * loadIdentityManager * * @return the IdentityManager */ private static IdentityManager loadIdentityManager() { IdentityManager identityManager = null; if (configuration.getIdmImpl() == null) { LOGGER.warn("***** No Identity Manager specified. Authentication disabled."); } else { try { Object idm = Class.forName(configuration.getIdmImpl()).getConstructor(Map.class) .newInstance(configuration.getIdmArgs()); identityManager = (IdentityManager) idm; } catch (Exception ex) { logErrorAndExit("Error configuring Identity Manager implementation " + configuration.getIdmImpl(), ex, false, -3); } } return identityManager; } /** * loadAccessManager * * @return the AccessManager */ private static AccessManager loadAccessManager() { AccessManager accessManager = new FullAccessManager(); if (configuration.getAmImpl() == null && configuration.getIdmImpl() != null) { LOGGER.warn("***** no access manager specified. authenticated users can do anything."); } else if (configuration.getAmImpl() == null && configuration.getIdmImpl() == null) { LOGGER.warn("***** No access manager specified. users can do anything."); } else { try { Object am = Class.forName(configuration.getAmImpl()).getConstructor(Map.class) .newInstance(configuration.getAmArgs()); accessManager = (AccessManager) am; } catch (Exception ex) { logErrorAndExit("Error configuring acess manager implementation " + configuration.getAmImpl(), ex, false, -3); } } return accessManager; } /** * logErrorAndExit * * @param message * @param t * @param silent * @param status */ private static void logErrorAndExit(String message, Throwable t, boolean silent, int status) { logErrorAndExit(message, t, silent, true, status); } /** * logErrorAndExit * * @param message * @param t * @param silent * @param removePid * @param status */ private static void logErrorAndExit(String message, Throwable t, boolean silent, boolean removePid, int status) { if (t == null) { LOGGER.error(message); } else { LOGGER.error(message, t); } stopServer(silent, removePid); System.exit(status); } /** * getHandlersPipe * * @param identityManager * @param accessManager * @return a GracefulShutdownHandler */ private static GracefulShutdownHandler getHandlersPipe(final IdentityManager identityManager, final AccessManager accessManager) { PipedHttpHandler coreHandlerChain = new AccountInjectorHandler( new DbPropsInjectorHandler(new CollectionPropsInjectorHandler(new RequestDispacherHandler()))); PathHandler paths = path(); configuration.getMongoMounts().stream().forEach(m -> { String url = (String) m.get(Configuration.MONGO_MOUNT_WHERE_KEY); String db = (String) m.get(Configuration.MONGO_MOUNT_WHAT_KEY); paths.addPrefixPath(url, new RequestLoggerHandler(new CORSHandler( new RequestContextInjectorHandler(url, db, new OptionsHandler(new BodyInjectorHandler( new SecurityHandlerDispacher(coreHandlerChain, identityManager, accessManager))))))); LOGGER.info("URL {} bound to MongoDB resource {}", url, db); }); pipeStaticResourcesHandlers(configuration, paths, identityManager, accessManager); pipeApplicationLogicHandlers(configuration, paths, identityManager, accessManager); // pipe the auth tokens invalidation handler paths.addPrefixPath("/_authtokens", new RequestLoggerHandler(new CORSHandler( new SecurityHandlerDispacher(new AuthTokenHandler(), identityManager, new FullAccessManager())))); return buildGracefulShutdownHandler(paths); } /** * buildGracefulShutdownHandler * * @param paths * @return */ private static GracefulShutdownHandler buildGracefulShutdownHandler(PathHandler paths) { return new GracefulShutdownHandler(new RequestLimitingHandler( new RequestLimit(configuration.getRequestLimit()), new AllowedMethodsHandler( new BlockingHandler( new GzipEncodingHandler(new ErrorHandler(new HttpContinueAcceptingHandler(paths)), configuration.isForceGzipEncoding())), // allowed methods HttpString.tryFromString(RequestContext.METHOD.GET.name()), HttpString.tryFromString(RequestContext.METHOD.POST.name()), HttpString.tryFromString(RequestContext.METHOD.PUT.name()), HttpString.tryFromString(RequestContext.METHOD.DELETE.name()), HttpString.tryFromString(RequestContext.METHOD.PATCH.name()), HttpString.tryFromString(RequestContext.METHOD.OPTIONS.name())))); } /** * pipeStaticResourcesHandlers * * pipe the static resources specified in the configuration file * * @param conf * @param paths * @param identityManager * @param accessManager */ private static void pipeStaticResourcesHandlers(final Configuration conf, final PathHandler paths, final IdentityManager identityManager, final AccessManager accessManager) { if (conf.getStaticResourcesMounts() != null) { conf.getStaticResourcesMounts().stream().forEach(sr -> { try { String path = (String) sr.get(Configuration.STATIC_RESOURCES_MOUNT_WHAT_KEY); String where = (String) sr.get(Configuration.STATIC_RESOURCES_MOUNT_WHERE_KEY); String welcomeFile = (String) sr.get(Configuration.STATIC_RESOURCES_MOUNT_WELCOME_FILE_KEY); Boolean embedded = (Boolean) sr.get(Configuration.STATIC_RESOURCES_MOUNT_EMBEDDED_KEY); embedded = embedded == null ? false : embedded; // makes embedded optional with default to false Boolean secured = (Boolean) sr.get(Configuration.STATIC_RESOURCES_MOUNT_SECURED_KEY); secured = secured == null ? false : secured; // makes secured optional with default to false if (where == null || !where.startsWith("/")) { LOGGER.error("Cannot bind static resources to {}. parameter 'where' must start with /", where); return; } if (welcomeFile == null) { welcomeFile = "index.html"; } File file; if (embedded) { if (path.startsWith("/")) { LOGGER.error("Cannot bind embedded static resources to {}. parameter 'where'" + "cannot start with /. the path is relative to the jar root dir or classpath directory", where); return; } try { file = ResourcesExtractor.extract(path); if (ResourcesExtractor.isResourceInJar(path)) { TMP_EXTRACTED_FILES.put(path, file); LOGGER.info("Embedded static resources {} extracted in {}", path, file.toString()); } } catch (URISyntaxException | IOException ex) { LOGGER.error("Error extracting embedded static resource {}", path, ex); return; } catch (IllegalStateException ex) { LOGGER.error("Error extracting embedded static resource {}", path, ex); if ("browser".equals(path)) { LOGGER.error("**** Have you downloaded the HAL Browser submodule before building?"); LOGGER.error("**** To fix this, run: $ git submodule update --init --recursive"); } return; } } else if (!path.startsWith("/")) { // this is to allow specifying the configuration file path relative // to the jar (also working when running from classes) URL location = Bootstrapper.class.getProtectionDomain().getCodeSource().getLocation(); File locationFile = new File(location.getPath()); Path _path = Paths.get(locationFile.getParent().concat(File.separator).concat(path)); // normalize addresses https://issues.jboss.org/browse/UNDERTOW-742 file = _path.normalize().toFile(); } else { file = new File(path); } if (file.exists()) { ResourceHandler handler = resource(new FileResourceManager(file, 3)) .addWelcomeFiles(welcomeFile).setDirectoryListingEnabled(false); PipedHttpHandler ph; if (secured) { ph = new RequestLoggerHandler(new SecurityHandlerDispacher( new PipedWrappingHandler(null, handler), identityManager, accessManager)); } else { ph = new RequestLoggerHandler(handler); } paths.addPrefixPath(where, ph); LOGGER.info("URL {} bound to static resources {}. Access Manager: {}", where, file.getAbsolutePath(), secured); } else { LOGGER.error("Failed to bind URL {} to static resources {}. Directory does not exist.", where, path); } } catch (Throwable t) { LOGGER.error("Cannot bind static resources to {}", sr.get(Configuration.STATIC_RESOURCES_MOUNT_WHERE_KEY), t); } }); } } /** * pipeApplicationLogicHandlers * * @param conf * @param paths * @param identityManager * @param accessManager */ private static void pipeApplicationLogicHandlers(final Configuration conf, final PathHandler paths, final IdentityManager identityManager, final AccessManager accessManager) { if (conf.getApplicationLogicMounts() != null) { conf.getApplicationLogicMounts().stream().forEach(al -> { try { String alClazz = (String) al.get(Configuration.APPLICATION_LOGIC_MOUNT_WHAT_KEY); String alWhere = (String) al.get(Configuration.APPLICATION_LOGIC_MOUNT_WHERE_KEY); boolean alSecured = (Boolean) al.get(Configuration.APPLICATION_LOGIC_MOUNT_SECURED_KEY); Object alArgs = al.get(Configuration.APPLICATION_LOGIC_MOUNT_ARGS_KEY); if (alWhere == null || !alWhere.startsWith("/")) { LOGGER.error( "Cannot pipe application logic handler {}. Parameter 'where' must start with /", alWhere); return; } if (alArgs != null && !(alArgs instanceof Map)) { LOGGER.error("Cannot pipe application logic handler {}." + "Args are not defined as a map. It is a ", alWhere, alWhere.getClass()); return; } Object o = Class.forName(alClazz).getConstructor(PipedHttpHandler.class, Map.class) .newInstance(null, (Map) alArgs); if (o instanceof ApplicationLogicHandler) { ApplicationLogicHandler alHandler = (ApplicationLogicHandler) o; PipedHttpHandler handler = new RequestContextInjectorHandler("/_logic", "*", alHandler); if (alSecured) { paths.addPrefixPath("/_logic" + alWhere, new RequestLoggerHandler(new CORSHandler( new SecurityHandlerDispacher(handler, identityManager, accessManager)))); } else { paths.addPrefixPath("/_logic" + alWhere, new RequestLoggerHandler(new CORSHandler(new SecurityHandlerDispacher(handler, identityManager, new FullAccessManager())))); } LOGGER.info("URL {} bound to application logic handler {}." + " Access manager: {}", "/_logic" + alWhere, alClazz, alSecured); } else { LOGGER.error("Cannot pipe application logic handler {}." + " Class {} does not extend ApplicationLogicHandler", alWhere, alClazz); } } catch (Throwable t) { LOGGER.error("Cannot pipe application logic handler {}", al.get(Configuration.APPLICATION_LOGIC_MOUNT_WHERE_KEY), t); } }); } } /** * getConfiguration * * @return the global configuration */ public static Configuration getConfiguration() { return configuration; } }