blob: 0e53b07a0cf2f47b3d7ce227f9684b554ed38c9b [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.camel.component.servletlistener;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.apache.camel.ManagementStatisticsLevel;
import org.apache.camel.RoutesBuilder;
import org.apache.camel.builder.ErrorHandlerBuilderRef;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.properties.PropertiesComponent;
import org.apache.camel.management.DefaultManagementAgent;
import org.apache.camel.management.DefaultManagementLifecycleStrategy;
import org.apache.camel.management.DefaultManagementStrategy;
import org.apache.camel.management.ManagedManagementStrategy;
import org.apache.camel.model.RouteDefinition;
import org.apache.camel.model.RoutesDefinition;
import org.apache.camel.spi.ManagementStrategy;
import org.apache.camel.spi.Registry;
import org.apache.camel.util.CamelContextHelper;
import org.apache.camel.util.CastUtils;
import org.apache.camel.util.IOHelper;
import org.apache.camel.util.IntrospectionSupport;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.ResourceHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A {@link ServletContextListener} which is used to bootstrap
* {@link org.apache.camel.CamelContext} in web applications.
*
* @param <R> the type of the {@link Registry} being {@link #createRegistry() created}
*/
public abstract class CamelServletContextListener<R extends Registry> implements ServletContextListener {
/**
* instance is used for testing purpose
*/
public static ServletCamelContext instance;
/**
* Key to store the created {@link org.apache.camel.CamelContext} as an attribute on the {@link javax.servlet.ServletContext}.
*/
public static final String CAMEL_CONTEXT_KEY = "CamelContext";
protected static final Logger LOG = LoggerFactory.getLogger(CamelServletContextListener.class);
protected ServletCamelContext camelContext;
protected CamelContextLifecycle<R> camelContextLifecycle;
protected boolean test;
protected R registry;
@Override
public void contextInitialized(ServletContextEvent sce) {
LOG.info("CamelContextServletListener initializing ...");
// create jndi and camel context
try {
registry = createRegistry();
camelContext = new ServletCamelContext(registry, sce.getServletContext());
} catch (Exception e) {
throw new RuntimeException("Error creating CamelContext.", e);
}
// get the init parameters
Map<String, Object> map = extractInitParameters(sce);
// special for test parameter
String test = (String) map.remove("test");
if (test != null && "true".equalsIgnoreCase(test)) {
this.test = true;
}
LOG.trace("In test mode? {}", this.test);
// set properties on the camel context from the init parameters
try {
initPropertyPlaceholder(camelContext, map);
initJmx(camelContext, map);
initCamelContext(camelContext, map);
if (!map.isEmpty()) {
IntrospectionSupport.setProperties(camelContext, map);
}
} catch (Exception e) {
throw new RuntimeException("Error setting init parameters on CamelContext.", e);
}
// any custom CamelContextLifecycle
String lifecycle = (String) map.remove("CamelContextLifecycle");
if (lifecycle != null) {
try {
Class<CamelContextLifecycle<R>> clazz = CastUtils.cast(camelContext.getClassResolver().resolveMandatoryClass(lifecycle, CamelContextLifecycle.class));
camelContextLifecycle = camelContext.getInjector().newInstance(clazz);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Error creating CamelContextLifecycle class with name " + lifecycle, e);
}
}
try {
if (camelContextLifecycle != null) {
camelContextLifecycle.beforeAddRoutes(camelContext, registry);
}
} catch (Exception e) {
LOG.error("Error before adding routes to CamelContext.", e);
throw new RuntimeException("Error before adding routes to CamelContext.", e);
}
// get the routes and add to the CamelContext
List<Object> routes = extractRoutes(map);
for (Object route : routes) {
if (route instanceof RouteBuilder) {
try {
camelContext.addRoutes((RoutesBuilder) route);
} catch (Exception e) {
throw new RuntimeException("Error adding route " + route, e);
}
} else if (route instanceof Set) {
// its a set of route builders
for (Object routesBuilder : (Set<?>) route) {
try {
camelContext.addRoutes((RoutesBuilder) routesBuilder);
} catch (Exception e) {
throw new RuntimeException("Error adding route " + routesBuilder, e);
}
}
} else if (route instanceof RoutesDefinition) {
try {
camelContext.addRouteDefinitions(((RoutesDefinition) route).getRoutes());
} catch (Exception e) {
throw new RuntimeException("Error adding route(s) " + route, e);
}
} else if (route instanceof RouteDefinition) {
try {
camelContext.addRouteDefinition((RouteDefinition) route);
} catch (Exception e) {
throw new RuntimeException("Error adding route(s) " + route, e);
}
} else {
throw new IllegalArgumentException("Unsupported route: " + route);
}
}
// just log if we could not use all the parameters, as they may be used by others
if (!map.isEmpty()) {
LOG.info("There are {} ServletContext init parameters, unknown to Camel. Maybe they are used by other frameworks? [{}]", map.size(), map);
}
try {
if (camelContextLifecycle != null) {
camelContextLifecycle.afterAddRoutes(camelContext, registry);
}
} catch (Exception e) {
LOG.error("Error after adding routes to CamelContext.", e);
throw new RuntimeException("Error after adding routes to CamelContext.", e);
}
try {
if (camelContextLifecycle != null) {
camelContextLifecycle.beforeStart(camelContext, registry);
}
camelContext.start();
if (camelContextLifecycle != null) {
camelContextLifecycle.afterStart(camelContext, registry);
}
} catch (Exception e) {
LOG.error("Error starting CamelContext.", e);
throw new RuntimeException("Error starting CamelContext.", e);
}
if (this.test) {
instance = camelContext;
}
// store the CamelContext as an attribute
sce.getServletContext().setAttribute(CAMEL_CONTEXT_KEY, camelContext);
LOG.info("CamelContextServletListener initialized");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
LOG.info("CamelContextServletListener destroying ...");
if (camelContext != null) {
try {
if (camelContextLifecycle != null) {
camelContextLifecycle.beforeStop(camelContext, registry);
}
camelContext.stop();
if (camelContextLifecycle != null) {
camelContextLifecycle.afterStop(camelContext, registry);
}
} catch (Exception e) {
LOG.warn("Error stopping CamelContext. This exception will be ignored.", e);
}
}
camelContext = null;
registry = null;
instance = null;
// store the CamelContext as an attribute
sce.getServletContext().removeAttribute(CAMEL_CONTEXT_KEY);
LOG.info("CamelContextServletListener destroyed");
}
/**
* Creates the {@link Registry} implementation to use.
*/
protected abstract R createRegistry() throws Exception;
/**
* Extracts all the init parameters, and will do reference lookup in {@link #createRegistry() registry}
* in case the value starts with a {@code #} sign.
*/
private Map<String, Object> extractInitParameters(ServletContextEvent sce) {
// configure CamelContext with the init parameter
Map<String, Object> map = new LinkedHashMap<String, Object>();
Enumeration<?> names = sce.getServletContext().getInitParameterNames();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
String value = sce.getServletContext().getInitParameter(name);
if (ObjectHelper.isNotEmpty(value)) {
Object target = value;
if (value.startsWith("#")) {
// a reference lookup in registry
value = value.substring(1);
target = lookupRegistryByName(value);
LOG.debug("Resolved the servlet context's initialization parameter {} to {}", value, target);
}
map.put(name, target);
}
}
return map;
}
/**
* Initializes the property placeholders by registering the {@link PropertiesComponent} with
* the configuration from the given init parameters.
*/
private void initPropertyPlaceholder(ServletCamelContext camelContext, Map<String, Object> parameters) throws Exception {
// setup property placeholder first
Map<String, Object> properties = IntrospectionSupport.extractProperties(parameters, "propertyPlaceholder.");
if (properties != null && !properties.isEmpty()) {
PropertiesComponent pc = new PropertiesComponent();
IntrospectionSupport.setProperties(pc, properties);
// validate we could set all parameters
if (!properties.isEmpty()) {
throw new IllegalArgumentException("Error setting propertyPlaceholder parameters on CamelContext."
+ " There are " + properties.size() + " unknown parameters. [" + properties + "]");
}
// register the properties component
camelContext.addComponent("properties", pc);
}
}
/**
* Initializes JMX on {@link ServletCamelContext} with the configuration from the given init parameters.
*/
private void initJmx(ServletCamelContext camelContext, Map<String, Object> parameters) throws Exception {
// setup jmx
Map<String, Object> properties = IntrospectionSupport.extractProperties(parameters, "jmx.");
if (properties != null && !properties.isEmpty()) {
String disabled = (String) properties.remove("disabled");
boolean disableJmx = CamelContextHelper.parseBoolean(camelContext, disabled != null ? disabled : "false");
if (disableJmx) {
// disable JMX which is a bit special to do
LOG.info("JMXAgent disabled");
// clear the existing lifecycle strategies define by the DefaultCamelContext constructor
camelContext.getLifecycleStrategies().clear();
// no need to add a lifecycle strategy as we do not need one as JMX is disabled
camelContext.setManagementStrategy(new DefaultManagementStrategy());
} else {
LOG.info("JMXAgent enabled");
DefaultManagementAgent agent = new DefaultManagementAgent(camelContext);
IntrospectionSupport.setProperties(agent, properties);
ManagementStrategy managementStrategy = new ManagedManagementStrategy(camelContext, agent);
camelContext.setManagementStrategy(managementStrategy);
// clear the existing lifecycle strategies defined by the DefaultCamelContext constructor
camelContext.getLifecycleStrategies().clear();
camelContext.addLifecycleStrategy(new DefaultManagementLifecycleStrategy(camelContext));
// set additional configuration from agent
boolean onlyId = agent.getOnlyRegisterProcessorWithCustomId() != null && agent.getOnlyRegisterProcessorWithCustomId();
camelContext.getManagementStrategy().onlyManageProcessorWithCustomId(onlyId);
String statisticsLevel = (String) properties.remove("statisticsLevel");
if (statisticsLevel != null) {
camelContext.getManagementStrategy().setStatisticsLevel(ManagementStatisticsLevel.valueOf(statisticsLevel));
}
String loadStatisticsEnabled = (String) properties.remove("loadStatisticsEnabled");
Boolean statisticsEnabled = CamelContextHelper.parseBoolean(camelContext, loadStatisticsEnabled != null ? loadStatisticsEnabled : "true");
if (statisticsEnabled != null) {
camelContext.getManagementStrategy().setLoadStatisticsEnabled(statisticsEnabled);
}
}
// validate we could set all parameters
if (!properties.isEmpty()) {
throw new IllegalArgumentException("Error setting jmx parameters on CamelContext."
+ " There are " + properties.size() + " unknown parameters. [" + properties + "]");
}
}
}
/**
* Initializes the {@link ServletCamelContext} by setting the supported init parameters.
*/
private void initCamelContext(ServletCamelContext camelContext, Map<String, Object> parameters) throws Exception {
String messageHistory = (String) parameters.remove("messageHistory");
if (messageHistory != null) {
camelContext.setMessageHistory(CamelContextHelper.parseBoolean(camelContext, messageHistory));
}
String streamCache = (String) parameters.remove("streamCache");
if (streamCache != null) {
camelContext.setStreamCaching(CamelContextHelper.parseBoolean(camelContext, streamCache));
}
String trace = (String) parameters.remove("trace");
if (trace != null) {
camelContext.setTracing(CamelContextHelper.parseBoolean(camelContext, trace));
}
String delayer = (String) parameters.remove("delayer");
if (delayer != null) {
camelContext.setDelayer(CamelContextHelper.parseLong(camelContext, delayer));
}
String handleFault = (String) parameters.remove("handleFault");
if (handleFault != null) {
camelContext.setHandleFault(CamelContextHelper.parseBoolean(camelContext, handleFault));
}
String errorHandlerRef = (String) parameters.remove("errorHandlerRef");
if (errorHandlerRef != null) {
camelContext.setErrorHandlerBuilder(new ErrorHandlerBuilderRef(errorHandlerRef));
}
String autoStartup = (String) parameters.remove("autoStartup");
if (autoStartup != null) {
camelContext.setAutoStartup(CamelContextHelper.parseBoolean(camelContext, autoStartup));
}
String useMDCLogging = (String) parameters.remove("useMDCLogging");
if (useMDCLogging != null) {
camelContext.setUseMDCLogging(CamelContextHelper.parseBoolean(camelContext, useMDCLogging));
}
String useBreadcrumb = (String) parameters.remove("useBreadcrumb");
if (useBreadcrumb != null) {
camelContext.setUseBreadcrumb(CamelContextHelper.parseBoolean(camelContext, useBreadcrumb));
}
String managementNamePattern = (String) parameters.remove("managementNamePattern");
if (managementNamePattern != null) {
camelContext.getManagementNameStrategy().setNamePattern(managementNamePattern);
}
String threadNamePattern = (String) parameters.remove("threadNamePattern");
if (threadNamePattern != null) {
camelContext.getExecutorServiceManager().setThreadNamePattern(threadNamePattern);
}
// extract any additional properties. prefixes
Map<String, Object> properties = IntrospectionSupport.extractProperties(parameters, "properties.");
if (properties != null && !properties.isEmpty()) {
for (Map.Entry<String, Object> entry : properties.entrySet()) {
camelContext.getProperties().put(entry.getKey(), "" + entry.getValue());
}
}
}
/**
* Extract the routes from the parameters.
*
* @param map parameters
* @return a list of routes, which can be of different types. See source code for more details.
*/
private List<Object> extractRoutes(Map<String, Object> map) {
List<Object> answer = new ArrayList<Object>();
List<String> names = new ArrayList<String>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getKey().toLowerCase(Locale.UK).startsWith("routebuilder")) {
names.add(entry.getKey());
// we can have multiple values assigned, separated by comma, so create an iterator
String value = (String) entry.getValue();
Iterator<Object> it = ObjectHelper.createIterator(value);
while (it.hasNext()) {
value = (String) it.next();
if (ObjectHelper.isNotEmpty(value)) {
// trim value before usage, as people can indent the values
value = value.trim();
Object target = null;
if (value.startsWith("#")) {
// a reference lookup in jndi
value = value.substring(1);
target = lookupRegistryByName(value);
} else if (ResourceHelper.hasScheme(value)) {
// XML resource from classpath or file system
InputStream is = null;
try {
is = ResourceHelper.resolveMandatoryResourceAsInputStream(camelContext, value);
target = camelContext.loadRoutesDefinition(is);
} catch (Exception e) {
throw new RuntimeException("Error loading routes from resource: " + value, e);
} finally {
IOHelper.close(is, entry.getKey(), LOG);
}
} else if (value.startsWith("packagescan:")) {
// using package scanning
String path = value.substring(12);
Set<Class<?>> classes = camelContext.getPackageScanClassResolver().findImplementations(RouteBuilder.class, path);
if (!classes.isEmpty()) {
Set<RouteBuilder> builders = new LinkedHashSet<RouteBuilder>();
target = builders;
for (Class<?> clazz : classes) {
try {
RouteBuilder route = (RouteBuilder) camelContext.getInjector().newInstance(clazz);
builders.add(route);
} catch (Exception e) {
throw new RuntimeException("Error creating RouteBuilder " + clazz, e);
}
}
}
} else {
// assume its a FQN classname for a RouteBuilder class
try {
Class<RouteBuilder> clazz = camelContext.getClassResolver().resolveMandatoryClass(value, RouteBuilder.class);
target = camelContext.getInjector().newInstance(clazz);
} catch (Exception e) {
throw new RuntimeException("Error creating RouteBuilder " + value, e);
}
}
if (target != null) {
answer.add(target);
}
}
}
}
}
// after adding the route builders we should remove them from the map
for (String name : names) {
map.remove(name);
}
return answer;
}
private Object lookupRegistryByName(String name) {
return registry.lookupByName(name);
}
}