All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.dandelion.core.Context Maven / Gradle / Ivy

/*
 * [The "BSD licence"]
 * Copyright (c) 2013-2015 Dandelion
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 3. Neither the name of Dandelion nor the names of its contributors 
 * may be used to endorse or promote products derived from this software 
 * without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.github.dandelion.core;

import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;

import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.servlet.FilterConfig;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.dandelion.core.asset.locator.AssetLocator;
import com.github.dandelion.core.asset.processor.AssetProcessor;
import com.github.dandelion.core.asset.processor.AssetProcessorManager;
import com.github.dandelion.core.asset.versioning.AssetVersioningStrategy;
import com.github.dandelion.core.bundle.loader.BundleLoader;
import com.github.dandelion.core.bundle.loader.impl.DandelionBundleLoader;
import com.github.dandelion.core.cache.Cache;
import com.github.dandelion.core.cache.CacheManager;
import com.github.dandelion.core.cache.RequestCache;
import com.github.dandelion.core.cache.StandardCache;
import com.github.dandelion.core.cache.impl.MemoryRequestCache;
import com.github.dandelion.core.config.Configuration;
import com.github.dandelion.core.config.ConfigurationLoader;
import com.github.dandelion.core.config.Profile;
import com.github.dandelion.core.config.StandardConfigurationLoader;
import com.github.dandelion.core.jmx.DandelionRuntime;
import com.github.dandelion.core.storage.AssetStorage;
import com.github.dandelion.core.storage.BundleStorage;
import com.github.dandelion.core.storage.BundleStorageUnit;
import com.github.dandelion.core.storage.impl.MemoryAssetStorage;
import com.github.dandelion.core.util.ClassUtils;
import com.github.dandelion.core.util.LibraryDetector;
import com.github.dandelion.core.util.ServiceLoaderUtils;
import com.github.dandelion.core.util.StringUtils;
import com.github.dandelion.core.web.DandelionFilter;
import com.github.dandelion.core.web.RequestFlashData;
import com.github.dandelion.core.web.handler.HandlerChain;
import com.github.dandelion.core.web.handler.debug.DebugMenu;
import com.github.dandelion.core.web.handler.debug.DebugPage;

/**
 * 

* Holds the whole Dandelion context. *

*

* This class is in charge of discovering and storing several configuration * points, such as the configured {@link RequestCache} implementation or the * active {@link AssetProcessor}s. *

*

* There should be only one instance of this class per JVM. *

* * @author Thibault Duchateau * @since 0.10.0 */ public class Context { private static final Logger LOG = LoggerFactory.getLogger(Context.class); private List components; private RequestCache requestCache; private Cache requestFlashDataCache; private Map processorsMap; private Map versioningStrategyMap; private AssetVersioningStrategy activeVersioningStrategy; private List activeProcessors; private List bundleLoaders; private AssetProcessorManager assetProcessorManager; private CacheManager assetCacheManager; private Map assetLocatorsMap; private BundleStorage bundleStorage; private AssetStorage assetStorage; private Configuration configuration; private HandlerChain preHandlerChain; private HandlerChain postHandlerChain; private Map debugMenuMap; private Map debugPageMap; /** *

* Public constructor. *

* * @param filterConfig * The servlet filter configuration. */ public Context(FilterConfig filterConfig) { init(filterConfig); } /** *

* Performs all the required initializations of the Dandelion context. *

* * @param filterConfig * The servlet filter configuration. */ public void init(FilterConfig filterConfig) { initConfiguration(filterConfig); initComponents(); initBundleLoaders(); initAssetLocators(); initRequestCache(); initRequestFlashDataCache(); initAssetProcessors(); initAssetVersioning(); assetProcessorManager = new AssetProcessorManager(this); assetCacheManager = new CacheManager(this); initBundleStorage(); initAssetStorage(); initMBean(filterConfig); initHandlers(); initDebugMenus(); } public void initComponents() { LOG.info("Scanning for components"); components = ServiceLoaderUtils.getProvidersAsList(Component.class); Iterator i = components.iterator(); StringBuilder log = new StringBuilder(i.next().getName()); while (i.hasNext()) { log.append(", "); log.append(i.next().getName()); } LOG.info("Found component(s): {}", log.toString()); } /** *

* Returns an implementation of {@link ConfigurationLoader} using the * following strategy: *

*
    *
  1. Check first if the dandelion.confloader.class system * property is set and tries to instantiate it
  2. *
  3. Otherwise, instantiate the {@link StandardConfigurationLoader} which * reads properties files
  4. *
* * @return an implementation of {@link ConfigurationLoader}. */ public void initConfiguration(FilterConfig filterConfig) { LOG.info("Initializing configuration loader"); ConfigurationLoader configurationLoader = null; if (StringUtils.isNotBlank(System.getProperty(ConfigurationLoader.DANDELION_CONFLOADER_CLASS))) { Class clazz; try { clazz = ClassUtils.getClass(System.getProperty(ConfigurationLoader.DANDELION_CONFLOADER_CLASS)); configurationLoader = (ConfigurationLoader) ClassUtils.getNewInstance(clazz); } catch (Exception e) { LOG.warn("Unable to instantiate the configured {} due to a {} exception. Falling back to the default one.", ConfigurationLoader.DANDELION_CONFLOADER_CLASS, e.getClass().getName(), e); } } if (configurationLoader == null) { configurationLoader = new StandardConfigurationLoader(); } configuration = new Configuration(filterConfig, configurationLoader.loadUserConfiguration(), this); } /** *

* Initializes the asset versioning for the whole application. *

*/ public void initAssetVersioning() { LOG.info("Initializing asset versioning"); List availableStrategies = ServiceLoaderUtils .getProvidersAsList(AssetVersioningStrategy.class); versioningStrategyMap = new HashMap(); for (AssetVersioningStrategy strategy : availableStrategies) { LOG.info("Found asset versioning strategy: {}", strategy.getName()); versioningStrategyMap.put(strategy.getName(), strategy); } String desiredVersioningStrategy = configuration.getAssetVersioningStrategy().toLowerCase().trim(); if (StringUtils.isNotBlank(desiredVersioningStrategy)) { if (versioningStrategyMap.containsKey(desiredVersioningStrategy)) { activeVersioningStrategy = versioningStrategyMap.get(desiredVersioningStrategy); LOG.info("Selected asset versioning strategy: {}", activeVersioningStrategy.getName()); activeVersioningStrategy.init(this); } else { throw new DandelionException("The desired asset versioning strategy (" + desiredVersioningStrategy + ") hasn't been found among the available ones: " + versioningStrategyMap.keySet()); } } } /** *

* Initializes the {@link BundleLoader}s in a particular order: *

* *
    *
  1. First, the {@link VendorBundleLoader} which loads all "vendor bundles" *
  2. *
  3. Then, all service providers of the {@link BundleLoader} SPI present in * the classpath
  4. *
  5. Finally the {@link DandelionBundleLoader} which loads all * "user bundles"
  6. *
*/ public void initBundleLoaders() { LOG.info("Initializing bundle loaders"); bundleLoaders = new ArrayList(); // First register all bundle loaders except DandelionBundleLoader for (Component component : components) { if (!component.getName().equalsIgnoreCase(CoreComponent.COMPONENT_NAME)) { BundleLoader bundleLoader = component.getBundleLoader(this); bundleLoaders.add(bundleLoader); LOG.info("Found bundle loader: {}", bundleLoader.getName()); } } // Finally register the DandelionBundleLoader for (Component component : components) { if (component.getName().equalsIgnoreCase(CoreComponent.COMPONENT_NAME)) { BundleLoader bundleLoader = component.getBundleLoader(this); bundleLoaders.add(bundleLoader); LOG.info("Found bundle loader: {}", bundleLoader.getName()); } } Iterator i = bundleLoaders.iterator(); StringBuilder log = new StringBuilder(i.next().getName()); while (i.hasNext()) { log.append(", "); log.append(i.next().getName()); } LOG.info("Bundle loaders initialized: {}", log.toString()); } /** *

* Initialize the request flash data cache only if Thymeleaf is present in * the classpath. *

*/ public void initRequestFlashDataCache() { if (LibraryDetector.isThymeleafAvailable()) { requestFlashDataCache = new StandardCache(100); } } /** *

* Initialize the service provider of {@link RequestCache} to use for * caching. *

*/ public void initRequestCache() { LOG.info("Initializing asset caching system"); ServiceLoader assetCacheServiceLoader = ServiceLoader.load(RequestCache.class); Map caches = new HashMap(); for (RequestCache ac : assetCacheServiceLoader) { caches.put(ac.getCacheName().toLowerCase().trim(), ac); LOG.info("Found asset caching system: {}", ac.getCacheName()); } String desiredCacheName = configuration.getCacheName().toLowerCase().trim(); if (StringUtils.isNotBlank(desiredCacheName)) { if (caches.containsKey(desiredCacheName)) { requestCache = caches.get(desiredCacheName); } else { LOG.warn( "The desired caching system ({}) hasn't been found in the classpath. Did you forget to add a dependency? The default one will be used.", desiredCacheName); } } // If no caching system is detected, it defaults to memory caching if (requestCache == null) { requestCache = caches.get(MemoryRequestCache.CACHE_NAME); } requestCache.initCache(this); LOG.info("Asset cache system initialized: {}", requestCache.getCacheName()); } /** *

* Initializes all service providers of the {@link AssetLocator} SPI. The * order doesn't matter. *

*/ public void initAssetLocators() { LOG.info("Initializing asset locators"); ServiceLoader alServiceLoader = ServiceLoader.load(AssetLocator.class); assetLocatorsMap = new HashMap(); for (AssetLocator al : alServiceLoader) { al.initLocator(this); assetLocatorsMap.put(al.getLocationKey(), al); LOG.info("Found asset locator: {}", al.getLocationKey()); } } /** *

* Initializes all service providers of the {@link AssetProcessor} SPI and * stores them all in the {@link #processorsMap}. *

* *

* If minification is enabled, the {@link #activeProcessors} is filled with * default service providers. *

*/ public void initAssetProcessors() { LOG.info("Initializing asset processors"); ServiceLoader apServiceLoader = ServiceLoader.load(AssetProcessor.class); processorsMap = new HashMap(); activeProcessors = new ArrayList(); for (AssetProcessor ape : apServiceLoader) { processorsMap.put(ape.getProcessorKey().toLowerCase().trim(), ape); LOG.info("Found asset processor: {}", ape.getClass().getSimpleName()); } if (configuration.isAssetMinificationEnabled()) { LOG.info("Asset processors enabled."); for (String assetProcessorKey : configuration.getAssetProcessors()) { if (processorsMap.containsKey(assetProcessorKey)) { activeProcessors.add(processorsMap.get(assetProcessorKey)); LOG.info("Processor enabled: {}", processorsMap.get(assetProcessorKey).getProcessorKey()); } } } else { LOG.info("Asset processors disabled. All assets will be served as-is."); } } /** *

* Initializes the {@link BundleStorage} by using all configured * {@link BundleLoader}s. *

* *

* Once loader, some checks are performed on the {@link BundleStorage}. *

*/ public void initBundleStorage() { LOG.info("Initializing bundle storage"); bundleStorage = new BundleStorage(); List allBundles = new ArrayList(); // Vendor bundles for (BundleLoader bundleLoader : getBundleLoaders()) { LOG.debug("Loading bundles using the {}", bundleLoader.getClass().getSimpleName()); // Load all bundles using the current BundleLoader List loadedBundles = bundleLoader.getVendorBundles(); allBundles.addAll(loadedBundles); LOG.debug("Found {} bundle{}: {}", loadedBundles.size(), loadedBundles.size() <= 1 ? "" : "s", loadedBundles); bundleStorage.storeBundles(loadedBundles); } // Regular bundles for (BundleLoader bundleLoader : getBundleLoaders()) { LOG.debug("Loading bundles using the {}", bundleLoader.getClass().getSimpleName()); // Load all bundles using the current BundleLoader List loadedBundles = bundleLoader.getRegularBundles(); allBundles.addAll(loadedBundles); LOG.debug("Found {} bundle{}: {}", loadedBundles.size(), loadedBundles.size() <= 1 ? "" : "s", loadedBundles); bundleStorage.storeBundles(loadedBundles); } bundleStorage.consolidateBundles(allBundles); LOG.info("Bundle storage initialized with {} bundles", bundleStorage.getBundleDag().getVertexMap().size()); } public void initAssetStorage() { LOG.info("Initializing asset storage"); ServiceLoader asServiceLoader = ServiceLoader.load(AssetStorage.class); String desiredAssetStorage = configuration.getAssetStorage(); if (StringUtils.isNotBlank(desiredAssetStorage)) { for (AssetStorage assetStorage : asServiceLoader) { LOG.info("Found asset storage: {}", assetStorage.getClass().getSimpleName()); if (assetStorage.getName().equalsIgnoreCase(desiredAssetStorage.trim())) { this.assetStorage = assetStorage; } } } // If no caching system is detected, it defaults to memory caching if (assetStorage == null) { assetStorage = new MemoryAssetStorage(); } requestCache.initCache(this); LOG.info("Asset storage initialized with: {}", assetStorage.getName()); } /** *

* If JMX is enabled, initializes a MBean allowing to reload bundles and * access cache. *

* * @param filterConfig * The servlet filter configuration. */ public void initMBean(FilterConfig filterConfig) { if (configuration.isMonitoringJmxEnabled()) { try { MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer(); ObjectName name = new ObjectName("com.github.dandelion", "type", DandelionRuntime.class.getSimpleName()); if (!mbeanServer.isRegistered(name)) { mbeanServer.registerMBean(new DandelionRuntime(this, filterConfig), name); } } catch (final JMException e) { LOG.error("An exception occured while registering the DandelionRuntimeMBean", e); } } } /** *

* Initializes the handler chains to be invoked in the * {@link DandelionFilter} to preprocess requests and postprocess responses. *

*/ public void initHandlers() { LOG.info("Initializing handlers"); List preHandlers = new ArrayList(); List postHandlers = new ArrayList(); List allHandlers = ServiceLoaderUtils.getProvidersAsList(HandlerChain.class); for (HandlerChain handler : allHandlers) { if (handler.isAfterChaining()) { postHandlers.add(handler); } else { preHandlers.add(handler); } } // Sort all handlers using their rank Collections.sort(preHandlers); Collections.sort(postHandlers); // Build the pre-handlers chain Iterator preHandlerIterator = preHandlers.iterator(); HandlerChain preHandler = preHandlerIterator.next(); int index = 1; do { LOG.info("Pre-handler ({}/{}) {} (rank: {})", index, preHandlers.size(), preHandler.getClass().getSimpleName(), preHandler.getRank()); if (preHandlerIterator.hasNext()) { HandlerChain next = preHandlerIterator.next(); preHandler.setNext(next); preHandler = next; } index++; } while (index <= preHandlers.size()); this.preHandlerChain = preHandlers.get(0); // Build the post-handlers chain Iterator postHandlerIterator = postHandlers.iterator(); HandlerChain postHandler = postHandlerIterator.next(); index = 1; do { LOG.info("Post-handler ({}/{}) {} (rank: {})", index, postHandlers.size(), postHandler.getClass() .getSimpleName(), postHandler.getRank()); if (postHandlerIterator.hasNext()) { HandlerChain next = postHandlerIterator.next(); postHandler.setNext(next); postHandler = next; } index++; } while (index <= postHandlers.size()); this.postHandlerChain = postHandlers.get(0); } public void initDebugMenus() { debugMenuMap = new HashMap(); debugPageMap = new HashMap(); for (Component component : components) { DebugMenu componentDebugMenu = component.getDebugMenu(); debugMenuMap.put(componentDebugMenu.getDisplayName().trim().toLowerCase(), componentDebugMenu); for (DebugPage debugPage : componentDebugMenu.getPages()) { debugPageMap.put(debugPage.getId(), debugPage); } } } public void destroy() { if (configuration.isMonitoringJmxEnabled()) { try { MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer(); ObjectName name = new ObjectName("com.github.dandelion", "type", DandelionRuntime.class.getSimpleName()); if (mbeanServer.isRegistered(name)) { mbeanServer.unregisterMBean(name); } } catch (final JMException e) { LOG.error("An exception occured while unregistering the DandelionRuntimeMBean", e); } } } /** * @return the selected asset caching system. */ public RequestCache getCache() { return requestCache; } /** * @return the map of all available {@link AssetProcessor}. */ public Map getProcessorsMap() { return processorsMap; } public List getBundleLoaders() { return bundleLoaders; } /** * @return the map of all available {@link AssetLocator}. */ public Map getAssetLocatorsMap() { return assetLocatorsMap; } public List getActiveProcessors() { return activeProcessors; } public BundleStorage getBundleStorage() { return bundleStorage; } public AssetStorage getAssetStorage() { return assetStorage; } public AssetProcessorManager getProcessorManager() { return assetProcessorManager; } public CacheManager getCacheManager() { return assetCacheManager; } /** * @return the {@link Configuration} store associated to the Dandelion * {@link Context} . */ public Configuration getConfiguration() { return configuration; } /** * @return {@code true} if the current {@link Profile} is set to "dev" or any * other aliases present in {@link Profile#DEV_ALIASES}, * {@code false} otherwise. */ public boolean isDevProfileEnabled() { return Profile.DEFAULT_DEV_PROFILE.equals(this.configuration.getActiveProfile()); } /** * @return {@code true} if the current {@link Profile} is set to "prod" or * any other aliases present in {@link Profile#PROD_ALIASES}, * {@code false} otherwise. */ public boolean isProdProfileEnabled() { return Profile.DEFAULT_PROD_PROFILE.equals(this.configuration.getActiveProfile()); } /** * @return the active {@link AssetVersioningStrategy}. */ public AssetVersioningStrategy getActiveVersioningStrategy() { return this.activeVersioningStrategy; } /** * @return the map of all available {@link AssetVersioningStrategy}. */ public Map getVersioningStrategyMap() { return versioningStrategyMap; } /** * @return the {@link HandlerChain} to be invoked in the * {@link DandelionFilter} to preprocess requests. */ public HandlerChain getPreHandlerChain() { return preHandlerChain; } /** * @return the {@link HandlerChain} to be invoked in the * {@link DandelionFilter} to postprocess server responses. */ public HandlerChain getPostHandlerChain() { return postHandlerChain; } public Map getDebugMenuMap() { return debugMenuMap; } public Map getDebugPageMap() { return debugPageMap; } public Cache getRequestFlashDataCache() { return requestFlashDataCache; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy