001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.util;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.net.URL;
022import java.util.Enumeration;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Locale;
026import java.util.Map;
027import java.util.Properties;
028import java.util.Set;
029import java.util.SortedMap;
030import java.util.StringTokenizer;
031import java.util.TreeMap;
032
033import org.apache.camel.CamelContext;
034import org.apache.camel.Component;
035import org.apache.camel.Endpoint;
036import org.apache.camel.Exchange;
037import org.apache.camel.NoSuchBeanException;
038import org.apache.camel.NoSuchEndpointException;
039import org.apache.camel.component.properties.PropertiesComponent;
040import org.apache.camel.model.FromDefinition;
041import org.apache.camel.model.ProcessorDefinition;
042import org.apache.camel.model.ProcessorDefinitionHelper;
043import org.apache.camel.model.RouteDefinition;
044import org.apache.camel.spi.ClassResolver;
045import org.apache.camel.spi.RouteStartupOrder;
046import org.slf4j.Logger;
047import org.slf4j.LoggerFactory;
048
049import static org.apache.camel.util.ObjectHelper.isEmpty;
050import static org.apache.camel.util.ObjectHelper.isNotEmpty;
051import static org.apache.camel.util.ObjectHelper.notNull;
052
053/**
054 * A number of helper methods
055 *
056 * @version 
057 */
058public final class CamelContextHelper {
059    public static final String COMPONENT_BASE = "META-INF/services/org/apache/camel/component/";
060    public static final String COMPONENT_DESCRIPTOR = "META-INF/services/org/apache/camel/component.properties";
061    public static final String COMPONENT_DOCUMENTATION_PREFIX = "org/apache/camel/component/";
062    public static final String MODEL_DESCRIPTOR = "META-INF/services/org/apache/camel/model.properties";
063    public static final String MODEL_DOCUMENTATION_PREFIX = "org/apache/camel/model/";
064
065    private static final Logger LOG = LoggerFactory.getLogger(CamelContextHelper.class);
066
067    /**
068     * Utility classes should not have a public constructor.
069     */
070    private CamelContextHelper() {
071    }
072
073    /**
074     * Returns the mandatory endpoint for the given URI or the
075     * {@link org.apache.camel.NoSuchEndpointException} is thrown
076     */
077    public static Endpoint getMandatoryEndpoint(CamelContext camelContext, String uri)
078        throws NoSuchEndpointException {
079        Endpoint endpoint = camelContext.getEndpoint(uri);
080        if (endpoint == null) {
081            throw new NoSuchEndpointException(uri);
082        } else {
083            return endpoint;
084        }
085    }
086
087    /**
088     * Returns the mandatory endpoint for the given URI and type or the
089     * {@link org.apache.camel.NoSuchEndpointException} is thrown
090     */
091    public static <T extends Endpoint> T getMandatoryEndpoint(CamelContext camelContext, String uri, Class<T> type) {
092        Endpoint endpoint = getMandatoryEndpoint(camelContext, uri);
093        return ObjectHelper.cast(type, endpoint);
094    }
095
096    /**
097     * Converts the given value to the requested type
098     */
099    public static <T> T convertTo(CamelContext context, Class<T> type, Object value) {
100        notNull(context, "camelContext");
101        return context.getTypeConverter().convertTo(type, value);
102    }
103
104    /**
105     * Converts the given value to the specified type throwing an {@link IllegalArgumentException}
106     * if the value could not be converted to a non null value
107     */
108    public static <T> T mandatoryConvertTo(CamelContext context, Class<T> type, Object value) {
109        T answer = convertTo(context, type, value);
110        if (answer == null) {
111            throw new IllegalArgumentException("Value " + value + " converted to " + type.getName() + " cannot be null");
112        }
113        return answer;
114    }
115
116    /**
117     * Creates a new instance of the given type using the {@link org.apache.camel.spi.Injector} on the given
118     * {@link CamelContext}
119     */
120    public static <T> T newInstance(CamelContext context, Class<T> beanType) {
121        return context.getInjector().newInstance(beanType);
122    }
123
124    /**
125     * Look up the given named bean in the {@link org.apache.camel.spi.Registry} on the
126     * {@link CamelContext}
127     */
128    public static Object lookup(CamelContext context, String name) {
129        return context.getRegistry().lookupByName(name);
130    }
131
132    /**
133     * Look up the given named bean of the given type in the {@link org.apache.camel.spi.Registry} on the
134     * {@link CamelContext}
135     */
136    public static <T> T lookup(CamelContext context, String name, Class<T> beanType) {
137        return context.getRegistry().lookupByNameAndType(name, beanType);
138    }
139
140    /**
141     * Look up the given named bean in the {@link org.apache.camel.spi.Registry} on the
142     * {@link CamelContext} or throws {@link NoSuchBeanException} if not found.
143     */
144    public static Object mandatoryLookup(CamelContext context, String name) {
145        Object answer = lookup(context, name);
146        if (answer == null) {
147            throw new NoSuchBeanException(name);
148        }
149        return answer;
150    }
151
152    /**
153     * Look up the given named bean of the given type in the {@link org.apache.camel.spi.Registry} on the
154     * {@link CamelContext} or throws NoSuchBeanException if not found.
155     */
156    public static <T> T mandatoryLookup(CamelContext context, String name, Class<T> beanType) {
157        T answer = lookup(context, name, beanType);
158        if (answer == null) {
159            throw new NoSuchBeanException(name, beanType.getName());
160        }
161        return answer;
162    }
163
164    /**
165     * Evaluates the @EndpointInject annotation using the given context
166     */
167    public static Endpoint getEndpointInjection(CamelContext camelContext, String uri, String ref, String injectionPointName, boolean mandatory) {
168        if (ObjectHelper.isNotEmpty(uri) && ObjectHelper.isNotEmpty(ref)) {
169            throw new IllegalArgumentException("Both uri and name is provided, only either one is allowed: uri=" + uri + ", ref=" + ref);
170        }
171
172        Endpoint endpoint;
173        if (isNotEmpty(uri)) {
174            endpoint = camelContext.getEndpoint(uri);
175        } else {
176            // if a ref is given then it should be possible to lookup
177            // otherwise we do not catch situations where there is a typo etc
178            if (isNotEmpty(ref)) {
179                endpoint = mandatoryLookup(camelContext, ref, Endpoint.class);
180            } else {
181                if (isEmpty(ref)) {
182                    ref = injectionPointName;
183                }
184                if (mandatory) {
185                    endpoint = mandatoryLookup(camelContext, ref, Endpoint.class);
186                } else {
187                    endpoint = lookup(camelContext, ref, Endpoint.class);
188                }
189            }
190        }
191        return endpoint;
192    }
193
194    /**
195     * Gets the maximum cache pool size.
196     * <p/>
197     * Will use the property set on CamelContext with the key {@link Exchange#MAXIMUM_CACHE_POOL_SIZE}.
198     * If no property has been set, then it will fallback to return a size of 1000.
199     *
200     * @param camelContext the camel context
201     * @return the maximum cache size
202     * @throws IllegalArgumentException is thrown if the property is illegal
203     */
204    public static int getMaximumCachePoolSize(CamelContext camelContext) throws IllegalArgumentException {
205        if (camelContext != null) {
206            String s = camelContext.getProperty(Exchange.MAXIMUM_CACHE_POOL_SIZE);
207            if (s != null) {
208                try {
209                    // we cannot use Camel type converters as they may not be ready this early
210                    Integer size = Integer.valueOf(s);
211                    if (size == null || size <= 0) {
212                        throw new IllegalArgumentException("Property " + Exchange.MAXIMUM_CACHE_POOL_SIZE + " must be a positive number, was: " + s);
213                    }
214                    return size;
215                } catch (NumberFormatException e) {
216                    throw new IllegalArgumentException("Property " + Exchange.MAXIMUM_CACHE_POOL_SIZE + " must be a positive number, was: " + s, e);
217                }
218            }
219        }
220
221        // 1000 is the default fallback
222        return 1000;
223    }
224
225    /**
226     * Gets the maximum endpoint cache size.
227     * <p/>
228     * Will use the property set on CamelContext with the key {@link Exchange#MAXIMUM_ENDPOINT_CACHE_SIZE}.
229     * If no property has been set, then it will fallback to return a size of 1000.
230     *
231     * @param camelContext the camel context
232     * @return the maximum cache size
233     * @throws IllegalArgumentException is thrown if the property is illegal
234     */
235    public static int getMaximumEndpointCacheSize(CamelContext camelContext) throws IllegalArgumentException {
236        if (camelContext != null) {
237            String s = camelContext.getProperty(Exchange.MAXIMUM_ENDPOINT_CACHE_SIZE);
238            if (s != null) {
239                // we cannot use Camel type converters as they may not be ready this early
240                try {
241                    Integer size = Integer.valueOf(s);
242                    if (size == null || size <= 0) {
243                        throw new IllegalArgumentException("Property " + Exchange.MAXIMUM_ENDPOINT_CACHE_SIZE + " must be a positive number, was: " + s);
244                    }
245                    return size;
246                } catch (NumberFormatException e) {
247                    throw new IllegalArgumentException("Property " + Exchange.MAXIMUM_ENDPOINT_CACHE_SIZE + " must be a positive number, was: " + s, e);
248                }
249            }
250        }
251
252        // 1000 is the default fallback
253        return 1000;
254    }
255
256    /**
257     * Parses the given text and handling property placeholders as well
258     *
259     * @param camelContext the camel context
260     * @param text  the text
261     * @return the parsed text, or <tt>null</tt> if the text was <tt>null</tt>
262     * @throws Exception is thrown if illegal argument
263     */
264    public static String parseText(CamelContext camelContext, String text) throws Exception {
265        // ensure we support property placeholders
266        return camelContext.resolvePropertyPlaceholders(text);
267    }
268
269    /**
270     * Parses the given text and converts it to an Integer and handling property placeholders as well
271     *
272     * @param camelContext the camel context
273     * @param text  the text
274     * @return the integer vale, or <tt>null</tt> if the text was <tt>null</tt>
275     * @throws Exception is thrown if illegal argument or type conversion not possible
276     */
277    public static Integer parseInteger(CamelContext camelContext, String text) throws Exception {
278        // ensure we support property placeholders
279        String s = camelContext.resolvePropertyPlaceholders(text);
280        if (s != null) {
281            try {
282                return camelContext.getTypeConverter().mandatoryConvertTo(Integer.class, s);
283            } catch (NumberFormatException e) {
284                if (s.equals(text)) {
285                    throw new IllegalArgumentException("Error parsing [" + s + "] as an Integer.", e);
286                } else {
287                    throw new IllegalArgumentException("Error parsing [" + s + "] from property " + text + " as an Integer.", e);
288                }
289            }
290        }
291        return null;
292    }
293
294    /**
295     * Parses the given text and converts it to an Long and handling property placeholders as well
296     *
297     * @param camelContext the camel context
298     * @param text  the text
299     * @return the long vale, or <tt>null</tt> if the text was <tt>null</tt>
300     * @throws Exception is thrown if illegal argument or type conversion not possible
301     */
302    public static Long parseLong(CamelContext camelContext, String text) throws Exception {
303        // ensure we support property placeholders
304        String s = camelContext.resolvePropertyPlaceholders(text);
305        if (s != null) {
306            try {
307                return camelContext.getTypeConverter().mandatoryConvertTo(Long.class, s);
308            } catch (NumberFormatException e) {
309                if (s.equals(text)) {
310                    throw new IllegalArgumentException("Error parsing [" + s + "] as a Long.", e);
311                } else {
312                    throw new IllegalArgumentException("Error parsing [" + s + "] from property " + text + " as a Long.", e);
313                }
314            }
315        }
316        return null;
317    }
318
319    /**
320     * Parses the given text and converts it to a Double and handling property placeholders as well
321     *
322     * @param camelContext the camel context
323     * @param text  the text
324     * @return the double vale, or <tt>null</tt> if the text was <tt>null</tt>
325     * @throws Exception is thrown if illegal argument or type conversion not possible
326     */
327    public static Double parseDouble(CamelContext camelContext, String text) throws Exception {
328        // ensure we support property placeholders
329        String s = camelContext.resolvePropertyPlaceholders(text);
330        if (s != null) {
331            try {
332                return camelContext.getTypeConverter().mandatoryConvertTo(Double.class, s);
333            } catch (NumberFormatException e) {
334                if (s.equals(text)) {
335                    throw new IllegalArgumentException("Error parsing [" + s + "] as an Integer.", e);
336                } else {
337                    throw new IllegalArgumentException("Error parsing [" + s + "] from property " + text + " as an Integer.", e);
338                }
339            }
340        }
341        return null;
342    }
343
344    /**
345     * Parses the given text and converts it to an Boolean and handling property placeholders as well
346     *
347     * @param camelContext the camel context
348     * @param text  the text
349     * @return the boolean vale, or <tt>null</tt> if the text was <tt>null</tt>
350     * @throws Exception is thrown if illegal argument or type conversion not possible
351     */
352    public static Boolean parseBoolean(CamelContext camelContext, String text) throws Exception {
353        // ensure we support property placeholders
354        String s = camelContext.resolvePropertyPlaceholders(text);
355        if (s != null) {
356            s = s.trim().toLowerCase(Locale.ENGLISH);
357            if (s.equals("true") || s.equals("false")) {
358                return "true".equals(s) ? Boolean.TRUE : Boolean.FALSE;
359            } else {
360                if (s.equals(text)) {
361                    throw new IllegalArgumentException("Error parsing [" + s + "] as a Boolean.");
362                } else {
363                    throw new IllegalArgumentException("Error parsing [" + s + "] from property " + text + " as a Boolean.");
364                }
365            }
366        }
367        return null;
368    }
369
370    /**
371     * Finds all possible Components on the classpath, already registered in {@link org.apache.camel.CamelContext},
372     * and from the {@link org.apache.camel.spi.Registry}.
373     */
374    public static SortedMap<String, Properties> findComponents(CamelContext camelContext) throws LoadPropertiesException {
375        ClassResolver resolver = camelContext.getClassResolver();
376        LOG.debug("Finding all components using class resolver: {} -> {}", new Object[]{resolver});
377        Enumeration<URL> iter = resolver.loadAllResourcesAsURL(COMPONENT_DESCRIPTOR);
378        return findComponents(camelContext, iter);
379    }
380
381    public static SortedMap<String, Properties> findComponents(CamelContext camelContext, Enumeration<URL> componentDescriptionIter)
382        throws LoadPropertiesException {
383
384        SortedMap<String, Properties> map = new TreeMap<String, Properties>();
385        while (componentDescriptionIter != null && componentDescriptionIter.hasMoreElements()) {
386            URL url = componentDescriptionIter.nextElement();
387            LOG.trace("Finding components in url: {}", url);
388            try {
389                Properties properties = new Properties();
390                properties.load(url.openStream());
391                String names = properties.getProperty("components");
392                if (names != null) {
393                    StringTokenizer tok = new StringTokenizer(names);
394                    while (tok.hasMoreTokens()) {
395                        String name = tok.nextToken();
396
397                        // try to find the class name for this component
398                        String className = null;
399                        InputStream is = null;
400                        try {
401                            // now load the component name resource so we can grab its properties and the class name
402                            Enumeration<URL> urls = camelContext.getClassResolver().loadAllResourcesAsURL(COMPONENT_BASE + name);
403                            if (urls != null && urls.hasMoreElements()) {
404                                is = urls.nextElement().openStream();
405                            }
406                            if (is != null) {
407                                Properties compProperties = new Properties();
408                                compProperties.load(is);
409                                if (!compProperties.isEmpty()) {
410                                    className = compProperties.getProperty("class");
411                                }
412                            }
413                        } catch (Exception e) {
414                            // ignore
415                        } finally {
416                            IOHelper.close(is);
417                        }
418
419                        // inherit properties we loaded first, as it has maven details
420                        Properties prop = new Properties();
421                        prop.putAll(properties);
422                        if (camelContext.hasComponent(name) != null) {
423                            prop.put("component", camelContext.getComponent(name));
424                        }
425                        if (className != null) {
426                            prop.put("class", className);
427                        }
428                        prop.put("name", name);
429                        map.put(name, prop);
430                    }
431                }
432            } catch (IOException e) {
433                throw new LoadPropertiesException(url, e);
434            }
435        }
436
437        // lets see what other components are registered on camel context
438        List<String> names = camelContext.getComponentNames();
439        for (String name : names) {
440            if (!map.containsKey(name)) {
441                Component component = camelContext.getComponent(name);
442                if (component != null) {
443                    Properties properties = new Properties();
444                    properties.put("component", component);
445                    properties.put("class", component.getClass().getName());
446                    properties.put("name", name);
447                    // override default component if name clash
448                    map.put(name, properties);
449                }
450            }
451        }
452
453        // lets see what other components are in the registry
454        Map<String, Component> beanMap = camelContext.getRegistry().findByTypeWithName(Component.class);
455        Set<Map.Entry<String, Component>> entries = beanMap.entrySet();
456        for (Map.Entry<String, Component> entry : entries) {
457            String name = entry.getKey();
458            if (!map.containsKey(name)) {
459                Component component = entry.getValue();
460                if (component != null) {
461                    Properties properties = new Properties();
462                    properties.put("component", component);
463                    properties.put("class", component.getClass().getName());
464                    properties.put("name", name);
465                    map.put(name, properties);
466                }
467            }
468        }
469        return map;
470    }
471
472    /**
473     * Find information about all the EIPs from camel-core.
474     */
475    public static SortedMap<String, Properties> findEips(CamelContext camelContext) throws LoadPropertiesException {
476        SortedMap<String, Properties> answer = new TreeMap<String, Properties>();
477
478        ClassResolver resolver = camelContext.getClassResolver();
479        LOG.debug("Finding all EIPs using class resolver: {} -> {}", new Object[]{resolver});
480        URL url = resolver.loadResourceAsURL(MODEL_DESCRIPTOR);
481        if (url != null) {
482            InputStream is = null;
483            try {
484                is = url.openStream();
485                String all = IOHelper.loadText(is);
486                String[] lines = all.split("\n");
487                for (String line : lines) {
488                    if (line.startsWith("#")) {
489                        continue;
490                    }
491
492                    Properties prop = new Properties();
493                    prop.put("name", line);
494
495                    String description = null;
496                    String label = null;
497                    String javaType = null;
498                    String title = null;
499
500                    // enrich with more meta-data
501                    String json = camelContext.explainEipJson(line, false);
502                    if (json != null) {
503                        List<Map<String, String>> rows = JsonSchemaHelper.parseJsonSchema("model", json, false);
504
505                        for (Map<String, String> row : rows) {
506                            if (row.get("title") != null) {
507                                title = row.get("title");
508                            }
509                            if (row.get("description") != null) {
510                                description = row.get("description");
511                            }
512                            if (row.get("label") != null) {
513                                label = row.get("label");
514                            }
515                            if (row.get("javaType") != null) {
516                                javaType = row.get("javaType");
517                            }
518                        }
519                    }
520
521                    if (title != null) {
522                        prop.put("title", title);
523                    }
524                    if (description != null) {
525                        prop.put("description", description);
526                    }
527                    if (label != null) {
528                        prop.put("label", label);
529                    }
530                    if (javaType != null) {
531                        prop.put("class", javaType);
532                    }
533
534                    answer.put(line, prop);
535                }
536            } catch (IOException e) {
537                throw new LoadPropertiesException(url, e);
538            } finally {
539                IOHelper.close(is);
540            }
541        }
542
543        return answer;
544    }
545
546    /**
547     * Gets the route startup order for the given route id
548     *
549     * @param camelContext  the camel context
550     * @param routeId       the id of the route
551     * @return the startup order, or <tt>0</tt> if not possible to determine
552     */
553    public static int getRouteStartupOrder(CamelContext camelContext, String routeId) {
554        for (RouteStartupOrder order : camelContext.getRouteStartupOrder()) {
555            if (order.getRoute().getId().equals(routeId)) {
556                return order.getStartupOrder();
557            }
558        }
559        return 0;
560    }
561
562    /**
563     * Lookup the {@link org.apache.camel.component.properties.PropertiesComponent} from the {@link org.apache.camel.CamelContext}.
564     * <p/>
565     * @param camelContext the camel context
566     * @param autoCreate whether to automatic create a new default {@link org.apache.camel.component.properties.PropertiesComponent} if no custom component
567     *                   has been configured.
568     * @return the properties component, or <tt>null</tt> if none has been defined, and auto create is <tt>false</tt>.
569     */
570    public static Component lookupPropertiesComponent(CamelContext camelContext, boolean autoCreate) {
571        // no existing properties component so lookup and add as component if possible
572        PropertiesComponent answer = (PropertiesComponent) camelContext.hasComponent("properties");
573        if (answer == null) {
574            // lookup what is stored under properties, as it may not be the Camel properties component
575            Object found = camelContext.getRegistry().lookupByName("properties");
576            if (found != null && found instanceof PropertiesComponent) {
577                answer = (PropertiesComponent) found;
578                camelContext.addComponent("properties", answer);
579            }
580        }
581        if (answer == null && autoCreate) {
582            // create a default properties component to be used as there may be default values we can use
583            LOG.info("No existing PropertiesComponent has been configured, creating a new default PropertiesComponent with name: properties");
584            // do not auto create using getComponent as spring auto-wire by constructor causes a side effect
585            answer = new PropertiesComponent();
586            camelContext.addComponent("properties", answer);
587        }
588        return answer;
589    }
590
591    /**
592     * Checks if any of the Camel routes is using an EIP with the given name
593     *
594     * @param camelContext  the Camel context
595     * @param name          the name of the EIP
596     * @return <tt>true</tt> if in use, <tt>false</tt> if not
597     */
598    public static boolean isEipInUse(CamelContext camelContext, String name) {
599        for (RouteDefinition route : camelContext.getRouteDefinitions()) {
600            for (FromDefinition from : route.getInputs()) {
601                if (name.equals(from.getShortName())) {
602                    return true;
603                }
604            }
605            Iterator<ProcessorDefinition> it = ProcessorDefinitionHelper.filterTypeInOutputs(route.getOutputs(), ProcessorDefinition.class);
606            while (it.hasNext()) {
607                ProcessorDefinition def = it.next();
608                if (name.equals(def.getShortName())) {
609                    return true;
610                }
611            }
612        }
613        return false;
614    }
615
616}