/** * 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.util; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.SortedMap; import java.util.StringTokenizer; import java.util.TreeMap; import org.apache.camel.CamelContext; import org.apache.camel.Component; import org.apache.camel.Endpoint; import org.apache.camel.Exchange; import org.apache.camel.NoSuchBeanException; import org.apache.camel.NoSuchEndpointException; import org.apache.camel.component.properties.PropertiesComponent; import org.apache.camel.model.FromDefinition; import org.apache.camel.model.ProcessorDefinition; import org.apache.camel.model.ProcessorDefinitionHelper; import org.apache.camel.model.RouteDefinition; import org.apache.camel.spi.ClassResolver; import org.apache.camel.spi.RouteStartupOrder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.apache.camel.util.ObjectHelper.isEmpty; import static org.apache.camel.util.ObjectHelper.isNotEmpty; import static org.apache.camel.util.ObjectHelper.notNull; /** * A number of helper methods * * @version */ public final class CamelContextHelper { public static final String COMPONENT_BASE = "META-INF/services/org/apache/camel/component/"; public static final String COMPONENT_DESCRIPTOR = "META-INF/services/org/apache/camel/component.properties"; public static final String COMPONENT_DOCUMENTATION_PREFIX = "org/apache/camel/component/"; public static final String MODEL_DESCRIPTOR = "META-INF/services/org/apache/camel/model.properties"; public static final String MODEL_DOCUMENTATION_PREFIX = "org/apache/camel/model/"; private static final Logger LOG = LoggerFactory.getLogger(CamelContextHelper.class); /** * Utility classes should not have a public constructor. */ private CamelContextHelper() { } /** * Returns the mandatory endpoint for the given URI or the * {@link org.apache.camel.NoSuchEndpointException} is thrown */ public static Endpoint getMandatoryEndpoint(CamelContext camelContext, String uri) throws NoSuchEndpointException { Endpoint endpoint = camelContext.getEndpoint(uri); if (endpoint == null) { throw new NoSuchEndpointException(uri); } else { return endpoint; } } /** * Returns the mandatory endpoint for the given URI and type or the * {@link org.apache.camel.NoSuchEndpointException} is thrown */ public static <T extends Endpoint> T getMandatoryEndpoint(CamelContext camelContext, String uri, Class<T> type) { Endpoint endpoint = getMandatoryEndpoint(camelContext, uri); return ObjectHelper.cast(type, endpoint); } /** * Converts the given value to the requested type */ public static <T> T convertTo(CamelContext context, Class<T> type, Object value) { notNull(context, "camelContext"); return context.getTypeConverter().convertTo(type, value); } /** * Converts the given value to the specified type throwing an {@link IllegalArgumentException} * if the value could not be converted to a non null value */ public static <T> T mandatoryConvertTo(CamelContext context, Class<T> type, Object value) { T answer = convertTo(context, type, value); if (answer == null) { throw new IllegalArgumentException("Value " + value + " converted to " + type.getName() + " cannot be null"); } return answer; } /** * Creates a new instance of the given type using the {@link org.apache.camel.spi.Injector} on the given * {@link CamelContext} */ public static <T> T newInstance(CamelContext context, Class<T> beanType) { return context.getInjector().newInstance(beanType); } /** * Look up the given named bean in the {@link org.apache.camel.spi.Registry} on the * {@link CamelContext} */ public static Object lookup(CamelContext context, String name) { return context.getRegistry().lookupByName(name); } /** * Look up the given named bean of the given type in the {@link org.apache.camel.spi.Registry} on the * {@link CamelContext} */ public static <T> T lookup(CamelContext context, String name, Class<T> beanType) { return context.getRegistry().lookupByNameAndType(name, beanType); } /** * Look up a bean of the give type in the {@link org.apache.camel.spi.Registry} on the * {@link CamelContext} returning an instance if only one bean is present, */ public static <T> T findByType(CamelContext camelContext, Class<T> type) { Set<T> set = camelContext.getRegistry().findByType(type); if (set.size() == 1) { return set.iterator().next(); } return null; } /** * Look up the given named bean in the {@link org.apache.camel.spi.Registry} on the * {@link CamelContext} or throws {@link NoSuchBeanException} if not found. */ public static Object mandatoryLookup(CamelContext context, String name) { Object answer = lookup(context, name); if (answer == null) { throw new NoSuchBeanException(name); } return answer; } /** * Look up the given named bean of the given type in the {@link org.apache.camel.spi.Registry} on the * {@link CamelContext} or throws NoSuchBeanException if not found. */ public static <T> T mandatoryLookup(CamelContext context, String name, Class<T> beanType) { T answer = lookup(context, name, beanType); if (answer == null) { throw new NoSuchBeanException(name, beanType.getName()); } return answer; } /** * Evaluates the @EndpointInject annotation using the given context */ public static Endpoint getEndpointInjection(CamelContext camelContext, String uri, String ref, String injectionPointName, boolean mandatory) { if (ObjectHelper.isNotEmpty(uri) && ObjectHelper.isNotEmpty(ref)) { throw new IllegalArgumentException("Both uri and name is provided, only either one is allowed: uri=" + uri + ", ref=" + ref); } Endpoint endpoint; if (isNotEmpty(uri)) { endpoint = camelContext.getEndpoint(uri); } else { // if a ref is given then it should be possible to lookup // otherwise we do not catch situations where there is a typo etc if (isNotEmpty(ref)) { endpoint = mandatoryLookup(camelContext, ref, Endpoint.class); } else { if (isEmpty(ref)) { ref = injectionPointName; } if (mandatory) { endpoint = mandatoryLookup(camelContext, ref, Endpoint.class); } else { endpoint = lookup(camelContext, ref, Endpoint.class); } } } return endpoint; } /** * Gets the maximum cache pool size. * <p/> * Will use the property set on CamelContext with the key {@link Exchange#MAXIMUM_CACHE_POOL_SIZE}. * If no property has been set, then it will fallback to return a size of 1000. * * @param camelContext the camel context * @return the maximum cache size * @throws IllegalArgumentException is thrown if the property is illegal */ public static int getMaximumCachePoolSize(CamelContext camelContext) throws IllegalArgumentException { if (camelContext != null) { String s = camelContext.getGlobalOption(Exchange.MAXIMUM_CACHE_POOL_SIZE); if (s != null) { try { // we cannot use Camel type converters as they may not be ready this early Integer size = Integer.valueOf(s); if (size == null || size <= 0) { throw new IllegalArgumentException("Property " + Exchange.MAXIMUM_CACHE_POOL_SIZE + " must be a positive number, was: " + s); } return size; } catch (NumberFormatException e) { throw new IllegalArgumentException("Property " + Exchange.MAXIMUM_CACHE_POOL_SIZE + " must be a positive number, was: " + s, e); } } } // 1000 is the default fallback return 1000; } /** * Gets the maximum endpoint cache size. * <p/> * Will use the property set on CamelContext with the key {@link Exchange#MAXIMUM_ENDPOINT_CACHE_SIZE}. * If no property has been set, then it will fallback to return a size of 1000. * * @param camelContext the camel context * @return the maximum cache size * @throws IllegalArgumentException is thrown if the property is illegal */ public static int getMaximumEndpointCacheSize(CamelContext camelContext) throws IllegalArgumentException { if (camelContext != null) { String s = camelContext.getGlobalOption(Exchange.MAXIMUM_ENDPOINT_CACHE_SIZE); if (s != null) { // we cannot use Camel type converters as they may not be ready this early try { Integer size = Integer.valueOf(s); if (size == null || size <= 0) { throw new IllegalArgumentException("Property " + Exchange.MAXIMUM_ENDPOINT_CACHE_SIZE + " must be a positive number, was: " + s); } return size; } catch (NumberFormatException e) { throw new IllegalArgumentException("Property " + Exchange.MAXIMUM_ENDPOINT_CACHE_SIZE + " must be a positive number, was: " + s, e); } } } // 1000 is the default fallback return 1000; } /** * Gets the maximum transformer cache size. * <p/> * Will use the property set on CamelContext with the key {@link Exchange#MAXIMUM_TRANSFORMER_CACHE_SIZE}. * If no property has been set, then it will fallback to return a size of 1000. * * @param camelContext the camel context * @return the maximum cache size * @throws IllegalArgumentException is thrown if the property is illegal */ public static int getMaximumTransformerCacheSize(CamelContext camelContext) throws IllegalArgumentException { if (camelContext != null) { String s = camelContext.getGlobalOption(Exchange.MAXIMUM_TRANSFORMER_CACHE_SIZE); if (s != null) { // we cannot use Camel type converters as they may not be ready this early try { Integer size = Integer.valueOf(s); if (size == null || size <= 0) { throw new IllegalArgumentException("Property " + Exchange.MAXIMUM_TRANSFORMER_CACHE_SIZE + " must be a positive number, was: " + s); } return size; } catch (NumberFormatException e) { throw new IllegalArgumentException("Property " + Exchange.MAXIMUM_TRANSFORMER_CACHE_SIZE + " must be a positive number, was: " + s, e); } } } // 1000 is the default fallback return 1000; } /** * Gets the maximum validator cache size. * <p/> * Will use the property set on CamelContext with the key {@link Exchange#MAXIMUM_VALIDATOR_CACHE_SIZE}. * If no property has been set, then it will fallback to return a size of 1000. * * @param camelContext the camel context * @return the maximum cache size * @throws IllegalArgumentException is thrown if the property is illegal */ public static int getMaximumValidatorCacheSize(CamelContext camelContext) throws IllegalArgumentException { if (camelContext != null) { String s = camelContext.getGlobalOption(Exchange.MAXIMUM_VALIDATOR_CACHE_SIZE); if (s != null) { // we cannot use Camel type converters as they may not be ready this early try { Integer size = Integer.valueOf(s); if (size == null || size <= 0) { throw new IllegalArgumentException("Property " + Exchange.MAXIMUM_VALIDATOR_CACHE_SIZE + " must be a positive number, was: " + s); } return size; } catch (NumberFormatException e) { throw new IllegalArgumentException("Property " + Exchange.MAXIMUM_VALIDATOR_CACHE_SIZE + " must be a positive number, was: " + s, e); } } } // 1000 is the default fallback return 1000; } /** * Parses the given text and handling property placeholders as well * * @param camelContext the camel context * @param text the text * @return the parsed text, or <tt>null</tt> if the text was <tt>null</tt> * @throws Exception is thrown if illegal argument */ public static String parseText(CamelContext camelContext, String text) throws Exception { // ensure we support property placeholders return camelContext.resolvePropertyPlaceholders(text); } /** * Parses the given text and converts it to an Integer and handling property placeholders as well * * @param camelContext the camel context * @param text the text * @return the integer vale, or <tt>null</tt> if the text was <tt>null</tt> * @throws Exception is thrown if illegal argument or type conversion not possible */ public static Integer parseInteger(CamelContext camelContext, String text) throws Exception { // ensure we support property placeholders String s = camelContext.resolvePropertyPlaceholders(text); if (s != null) { try { return camelContext.getTypeConverter().mandatoryConvertTo(Integer.class, s); } catch (NumberFormatException e) { if (s.equals(text)) { throw new IllegalArgumentException("Error parsing [" + s + "] as an Integer.", e); } else { throw new IllegalArgumentException("Error parsing [" + s + "] from property " + text + " as an Integer.", e); } } } return null; } /** * Parses the given text and converts it to an Long and handling property placeholders as well * * @param camelContext the camel context * @param text the text * @return the long vale, or <tt>null</tt> if the text was <tt>null</tt> * @throws Exception is thrown if illegal argument or type conversion not possible */ public static Long parseLong(CamelContext camelContext, String text) throws Exception { // ensure we support property placeholders String s = camelContext.resolvePropertyPlaceholders(text); if (s != null) { try { return camelContext.getTypeConverter().mandatoryConvertTo(Long.class, s); } catch (NumberFormatException e) { if (s.equals(text)) { throw new IllegalArgumentException("Error parsing [" + s + "] as a Long.", e); } else { throw new IllegalArgumentException("Error parsing [" + s + "] from property " + text + " as a Long.", e); } } } return null; } /** * Parses the given text and converts it to a Double and handling property placeholders as well * * @param camelContext the camel context * @param text the text * @return the double vale, or <tt>null</tt> if the text was <tt>null</tt> * @throws Exception is thrown if illegal argument or type conversion not possible */ public static Double parseDouble(CamelContext camelContext, String text) throws Exception { // ensure we support property placeholders String s = camelContext.resolvePropertyPlaceholders(text); if (s != null) { try { return camelContext.getTypeConverter().mandatoryConvertTo(Double.class, s); } catch (NumberFormatException e) { if (s.equals(text)) { throw new IllegalArgumentException("Error parsing [" + s + "] as an Integer.", e); } else { throw new IllegalArgumentException("Error parsing [" + s + "] from property " + text + " as an Integer.", e); } } } return null; } /** * Parses the given text and converts it to an Boolean and handling property placeholders as well * * @param camelContext the camel context * @param text the text * @return the boolean vale, or <tt>null</tt> if the text was <tt>null</tt> * @throws Exception is thrown if illegal argument or type conversion not possible */ public static Boolean parseBoolean(CamelContext camelContext, String text) throws Exception { // ensure we support property placeholders String s = camelContext.resolvePropertyPlaceholders(text); if (s != null) { s = s.trim().toLowerCase(Locale.ENGLISH); if (s.equals("true") || s.equals("false")) { return "true".equals(s) ? Boolean.TRUE : Boolean.FALSE; } else { if (s.equals(text)) { throw new IllegalArgumentException("Error parsing [" + s + "] as a Boolean."); } else { throw new IllegalArgumentException("Error parsing [" + s + "] from property " + text + " as a Boolean."); } } } return null; } /** * Finds all possible Components on the classpath, already registered in {@link org.apache.camel.CamelContext}, * and from the {@link org.apache.camel.spi.Registry}. */ public static SortedMap<String, Properties> findComponents(CamelContext camelContext) throws LoadPropertiesException { ClassResolver resolver = camelContext.getClassResolver(); LOG.debug("Finding all components using class resolver: {} -> {}", new Object[]{resolver}); Enumeration<URL> iter = resolver.loadAllResourcesAsURL(COMPONENT_DESCRIPTOR); return findComponents(camelContext, iter); } public static SortedMap<String, Properties> findComponents(CamelContext camelContext, Enumeration<URL> componentDescriptionIter) throws LoadPropertiesException { SortedMap<String, Properties> map = new TreeMap<String, Properties>(); while (componentDescriptionIter != null && componentDescriptionIter.hasMoreElements()) { URL url = componentDescriptionIter.nextElement(); LOG.trace("Finding components in url: {}", url); try { Properties properties = new Properties(); properties.load(url.openStream()); String names = properties.getProperty("components"); if (names != null) { StringTokenizer tok = new StringTokenizer(names); while (tok.hasMoreTokens()) { String name = tok.nextToken(); // try to find the class name for this component String className = null; InputStream is = null; try { // now load the component name resource so we can grab its properties and the class name Enumeration<URL> urls = camelContext.getClassResolver().loadAllResourcesAsURL(COMPONENT_BASE + name); if (urls != null && urls.hasMoreElements()) { is = urls.nextElement().openStream(); } if (is != null) { Properties compProperties = new Properties(); compProperties.load(is); if (!compProperties.isEmpty()) { className = compProperties.getProperty("class"); } } } catch (Exception e) { // ignore } finally { IOHelper.close(is); } // inherit properties we loaded first, as it has maven details Properties prop = new Properties(); prop.putAll(properties); if (camelContext.hasComponent(name) != null) { prop.put("component", camelContext.getComponent(name)); } if (className != null) { prop.put("class", className); } prop.put("name", name); map.put(name, prop); } } } catch (IOException e) { throw new LoadPropertiesException(url, e); } } // lets see what other components are registered on camel context List<String> names = camelContext.getComponentNames(); for (String name : names) { if (!map.containsKey(name)) { Component component = camelContext.getComponent(name); if (component != null) { Properties properties = new Properties(); properties.put("component", component); properties.put("class", component.getClass().getName()); properties.put("name", name); // override default component if name clash map.put(name, properties); } } } // lets see what other components are in the registry Map<String, Component> beanMap = camelContext.getRegistry().findByTypeWithName(Component.class); Set<Map.Entry<String, Component>> entries = beanMap.entrySet(); for (Map.Entry<String, Component> entry : entries) { String name = entry.getKey(); if (!map.containsKey(name)) { Component component = entry.getValue(); if (component != null) { Properties properties = new Properties(); properties.put("component", component); properties.put("class", component.getClass().getName()); properties.put("name", name); map.put(name, properties); } } } return map; } /** * Find information about all the EIPs from camel-core. */ public static SortedMap<String, Properties> findEips(CamelContext camelContext) throws LoadPropertiesException { SortedMap<String, Properties> answer = new TreeMap<String, Properties>(); ClassResolver resolver = camelContext.getClassResolver(); LOG.debug("Finding all EIPs using class resolver: {} -> {}", new Object[]{resolver}); URL url = resolver.loadResourceAsURL(MODEL_DESCRIPTOR); if (url != null) { InputStream is = null; try { is = url.openStream(); String all = IOHelper.loadText(is); String[] lines = all.split("\n"); for (String line : lines) { if (line.startsWith("#")) { continue; } Properties prop = new Properties(); prop.put("name", line); String description = null; String label = null; String javaType = null; String title = null; // enrich with more meta-data String json = camelContext.explainEipJson(line, false); if (json != null) { List<Map<String, String>> rows = JsonSchemaHelper.parseJsonSchema("model", json, false); for (Map<String, String> row : rows) { if (row.get("title") != null) { title = row.get("title"); } if (row.get("description") != null) { description = row.get("description"); } if (row.get("label") != null) { label = row.get("label"); } if (row.get("javaType") != null) { javaType = row.get("javaType"); } } } if (title != null) { prop.put("title", title); } if (description != null) { prop.put("description", description); } if (label != null) { prop.put("label", label); } if (javaType != null) { prop.put("class", javaType); } answer.put(line, prop); } } catch (IOException e) { throw new LoadPropertiesException(url, e); } finally { IOHelper.close(is); } } return answer; } /** * Gets the route startup order for the given route id * * @param camelContext the camel context * @param routeId the id of the route * @return the startup order, or <tt>0</tt> if not possible to determine */ public static int getRouteStartupOrder(CamelContext camelContext, String routeId) { for (RouteStartupOrder order : camelContext.getRouteStartupOrder()) { if (order.getRoute().getId().equals(routeId)) { return order.getStartupOrder(); } } return 0; } /** * Lookup the {@link org.apache.camel.component.properties.PropertiesComponent} from the {@link org.apache.camel.CamelContext}. * <p/> * @param camelContext the camel context * @param autoCreate whether to automatic create a new default {@link org.apache.camel.component.properties.PropertiesComponent} if no custom component * has been configured. * @return the properties component, or <tt>null</tt> if none has been defined, and auto create is <tt>false</tt>. */ public static Component lookupPropertiesComponent(CamelContext camelContext, boolean autoCreate) { // no existing properties component so lookup and add as component if possible PropertiesComponent answer = (PropertiesComponent) camelContext.hasComponent("properties"); if (answer == null) { // lookup what is stored under properties, as it may not be the Camel properties component Object found = camelContext.getRegistry().lookupByName("properties"); if (found != null && found instanceof PropertiesComponent) { answer = (PropertiesComponent) found; camelContext.addComponent("properties", answer); } } if (answer == null && autoCreate) { // create a default properties component to be used as there may be default values we can use LOG.info("No existing PropertiesComponent has been configured, creating a new default PropertiesComponent with name: properties"); // do not auto create using getComponent as spring auto-wire by constructor causes a side effect answer = new PropertiesComponent(true); camelContext.addComponent("properties", answer); } return answer; } /** * Checks if any of the Camel routes is using an EIP with the given name * * @param camelContext the Camel context * @param name the name of the EIP * @return <tt>true</tt> if in use, <tt>false</tt> if not */ public static boolean isEipInUse(CamelContext camelContext, String name) { for (RouteDefinition route : camelContext.getRouteDefinitions()) { for (FromDefinition from : route.getInputs()) { if (name.equals(from.getShortName())) { return true; } } Iterator<ProcessorDefinition> it = ProcessorDefinitionHelper.filterTypeInOutputs(route.getOutputs(), ProcessorDefinition.class); while (it.hasNext()) { ProcessorDefinition def = it.next(); if (name.equals(def.getShortName())) { return true; } } } return false; } /** * Inspects the given object and resolves any property placeholders from its properties. * <p/> * This implementation will check all the getter/setter pairs on this instance and for all the values * (which is a String type) will be property placeholder resolved. * * @param camelContext the Camel context * @param target the object that should have the properties (eg getter/setter) resolved * @throws Exception is thrown if property placeholders was used and there was an error resolving them * @see org.apache.camel.CamelContext#resolvePropertyPlaceholders(String) * @see org.apache.camel.component.properties.PropertiesComponent */ public static void resolvePropertyPlaceholders(CamelContext camelContext, Object target) throws Exception { LOG.trace("Resolving property placeholders for: {}", target); // find all getter/setter which we can use for property placeholders Map<String, Object> properties = new HashMap<String, Object>(); IntrospectionSupport.getProperties(target, properties, null); Map<String, Object> changedProperties = new HashMap<String, Object>(); if (!properties.isEmpty()) { LOG.trace("There are {} properties on: {}", properties.size(), target); // lookup and resolve properties for String based properties for (Map.Entry<String, Object> entry : properties.entrySet()) { // the name is always a String String name = entry.getKey(); Object value = entry.getValue(); if (value instanceof String) { // value must be a String, as a String is the key for a property placeholder String text = (String) value; text = camelContext.resolvePropertyPlaceholders(text); if (text != value) { // invoke setter as the text has changed boolean changed = IntrospectionSupport.setProperty(camelContext.getTypeConverter(), target, name, text); if (!changed) { throw new IllegalArgumentException("No setter to set property: " + name + " to: " + text + " on: " + target); } changedProperties.put(name, value); if (LOG.isDebugEnabled()) { LOG.debug("Changed property [{}] from: {} to: {}", new Object[]{name, value, text}); } } } } } } }