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     */
017    package org.apache.camel.util;
018    
019    import java.io.IOException;
020    import java.io.InputStream;
021    import java.net.URL;
022    import java.util.Enumeration;
023    import java.util.List;
024    import java.util.Locale;
025    import java.util.Map;
026    import java.util.Properties;
027    import java.util.Set;
028    import java.util.SortedMap;
029    import java.util.StringTokenizer;
030    import java.util.TreeMap;
031    
032    import org.apache.camel.CamelContext;
033    import org.apache.camel.Component;
034    import org.apache.camel.Endpoint;
035    import org.apache.camel.Exchange;
036    import org.apache.camel.NoSuchBeanException;
037    import org.apache.camel.NoSuchEndpointException;
038    import org.apache.camel.spi.ClassResolver;
039    import org.apache.camel.spi.RouteStartupOrder;
040    import org.slf4j.Logger;
041    import org.slf4j.LoggerFactory;
042    
043    import static org.apache.camel.util.ObjectHelper.isEmpty;
044    import static org.apache.camel.util.ObjectHelper.isNotEmpty;
045    import static org.apache.camel.util.ObjectHelper.notNull;
046    
047    /**
048     * A number of helper methods
049     *
050     * @version 
051     */
052    public final class CamelContextHelper {
053        public static final String COMPONENT_BASE = "META-INF/services/org/apache/camel/component/";
054        public static final String COMPONENT_DESCRIPTOR = "META-INF/services/org/apache/camel/component.properties";
055        public static final String COMPONENT_DOCUMENTATION_PREFIX = "org/apache/camel/component/";
056    
057        private static final Logger LOG = LoggerFactory.getLogger(CamelContextHelper.class);
058    
059        /**
060         * Utility classes should not have a public constructor.
061         */
062        private CamelContextHelper() {
063        }
064    
065        /**
066         * Returns the mandatory endpoint for the given URI or the
067         * {@link org.apache.camel.NoSuchEndpointException} is thrown
068         */
069        public static Endpoint getMandatoryEndpoint(CamelContext camelContext, String uri)
070            throws NoSuchEndpointException {
071            Endpoint endpoint = camelContext.getEndpoint(uri);
072            if (endpoint == null) {
073                throw new NoSuchEndpointException(uri);
074            } else {
075                return endpoint;
076            }
077        }
078    
079        /**
080         * Returns the mandatory endpoint for the given URI and type or the
081         * {@link org.apache.camel.NoSuchEndpointException} is thrown
082         */
083        public static <T extends Endpoint> T getMandatoryEndpoint(CamelContext camelContext, String uri, Class<T> type) {
084            Endpoint endpoint = getMandatoryEndpoint(camelContext, uri);
085            return ObjectHelper.cast(type, endpoint);
086        }
087    
088        /**
089         * Converts the given value to the requested type
090         */
091        public static <T> T convertTo(CamelContext context, Class<T> type, Object value) {
092            notNull(context, "camelContext");
093            return context.getTypeConverter().convertTo(type, value);
094        }
095    
096        /**
097         * Converts the given value to the specified type throwing an {@link IllegalArgumentException}
098         * if the value could not be converted to a non null value
099         */
100        public static <T> T mandatoryConvertTo(CamelContext context, Class<T> type, Object value) {
101            T answer = convertTo(context, type, value);
102            if (answer == null) {
103                throw new IllegalArgumentException("Value " + value + " converted to " + type.getName() + " cannot be null");
104            }
105            return answer;
106        }
107    
108        /**
109         * Creates a new instance of the given type using the {@link org.apache.camel.spi.Injector} on the given
110         * {@link CamelContext}
111         */
112        public static <T> T newInstance(CamelContext context, Class<T> beanType) {
113            return context.getInjector().newInstance(beanType);
114        }
115    
116        /**
117         * Look up the given named bean in the {@link org.apache.camel.spi.Registry} on the
118         * {@link CamelContext}
119         */
120        public static Object lookup(CamelContext context, String name) {
121            return context.getRegistry().lookupByName(name);
122        }
123    
124        /**
125         * Look up the given named bean of the given type in the {@link org.apache.camel.spi.Registry} on the
126         * {@link CamelContext}
127         */
128        public static <T> T lookup(CamelContext context, String name, Class<T> beanType) {
129            return context.getRegistry().lookupByNameAndType(name, beanType);
130        }
131    
132        /**
133         * Look up the given named bean in the {@link org.apache.camel.spi.Registry} on the
134         * {@link CamelContext} or throws {@link NoSuchBeanException} if not found.
135         */
136        public static Object mandatoryLookup(CamelContext context, String name) {
137            Object answer = lookup(context, name);
138            if (answer == null) {
139                throw new NoSuchBeanException(name);
140            }
141            return answer;
142        }
143    
144        /**
145         * Look up the given named bean of the given type in the {@link org.apache.camel.spi.Registry} on the
146         * {@link CamelContext} or throws NoSuchBeanException if not found.
147         */
148        public static <T> T mandatoryLookup(CamelContext context, String name, Class<T> beanType) {
149            T answer = lookup(context, name, beanType);
150            if (answer == null) {
151                throw new NoSuchBeanException(name, beanType.getName());
152            }
153            return answer;
154        }
155    
156        /**
157         * Evaluates the @EndpointInject annotation using the given context
158         */
159        public static Endpoint getEndpointInjection(CamelContext camelContext, String uri, String ref, String injectionPointName, boolean mandatory) {
160            if (ObjectHelper.isNotEmpty(uri) && ObjectHelper.isNotEmpty(ref)) {
161                throw new IllegalArgumentException("Both uri and name is provided, only either one is allowed: uri=" + uri + ", ref=" + ref);
162            }
163    
164            Endpoint endpoint;
165            if (isNotEmpty(uri)) {
166                endpoint = camelContext.getEndpoint(uri);
167            } else {
168                // if a ref is given then it should be possible to lookup
169                // otherwise we do not catch situations where there is a typo etc
170                if (isNotEmpty(ref)) {
171                    endpoint = mandatoryLookup(camelContext, ref, Endpoint.class);
172                } else {
173                    if (isEmpty(ref)) {
174                        ref = injectionPointName;
175                    }
176                    if (mandatory) {
177                        endpoint = mandatoryLookup(camelContext, ref, Endpoint.class);
178                    } else {
179                        endpoint = lookup(camelContext, ref, Endpoint.class);
180                    }
181                }
182            }
183            return endpoint;
184        }
185    
186        /**
187         * Gets the maximum cache pool size.
188         * <p/>
189         * Will use the property set on CamelContext with the key {@link Exchange#MAXIMUM_CACHE_POOL_SIZE}.
190         * If no property has been set, then it will fallback to return a size of 1000.
191         *
192         * @param camelContext the camel context
193         * @return the maximum cache size
194         * @throws IllegalArgumentException is thrown if the property is illegal
195         */
196        public static int getMaximumCachePoolSize(CamelContext camelContext) throws IllegalArgumentException {
197            if (camelContext != null) {
198                String s = camelContext.getProperty(Exchange.MAXIMUM_CACHE_POOL_SIZE);
199                if (s != null) {
200                    try {
201                        // we cannot use Camel type converters as they may not be ready this early
202                        Integer size = Integer.valueOf(s);
203                        if (size == null || size <= 0) {
204                            throw new IllegalArgumentException("Property " + Exchange.MAXIMUM_CACHE_POOL_SIZE + " must be a positive number, was: " + s);
205                        }
206                        return size;
207                    } catch (NumberFormatException e) {
208                        throw new IllegalArgumentException("Property " + Exchange.MAXIMUM_CACHE_POOL_SIZE + " must be a positive number, was: " + s, e);
209                    }
210                }
211            }
212    
213            // 1000 is the default fallback
214            return 1000;
215        }
216    
217        /**
218         * Gets the maximum endpoint cache size.
219         * <p/>
220         * Will use the property set on CamelContext with the key {@link Exchange#MAXIMUM_ENDPOINT_CACHE_SIZE}.
221         * If no property has been set, then it will fallback to return a size of 1000.
222         *
223         * @param camelContext the camel context
224         * @return the maximum cache size
225         * @throws IllegalArgumentException is thrown if the property is illegal
226         */
227        public static int getMaximumEndpointCacheSize(CamelContext camelContext) throws IllegalArgumentException {
228            if (camelContext != null) {
229                String s = camelContext.getProperty(Exchange.MAXIMUM_ENDPOINT_CACHE_SIZE);
230                if (s != null) {
231                    // we cannot use Camel type converters as they may not be ready this early
232                    try {
233                        Integer size = Integer.valueOf(s);
234                        if (size == null || size <= 0) {
235                            throw new IllegalArgumentException("Property " + Exchange.MAXIMUM_ENDPOINT_CACHE_SIZE + " must be a positive number, was: " + s);
236                        }
237                        return size;
238                    } catch (NumberFormatException e) {
239                        throw new IllegalArgumentException("Property " + Exchange.MAXIMUM_ENDPOINT_CACHE_SIZE + " must be a positive number, was: " + s, e);
240                    }
241                }
242            }
243    
244            // 1000 is the default fallback
245            return 1000;
246        }
247    
248        /**
249         * Parses the given text and handling property placeholders as well
250         *
251         * @param camelContext the camel context
252         * @param text  the text
253         * @return the parsed text, or <tt>null</tt> if the text was <tt>null</tt>
254         * @throws Exception is thrown if illegal argument
255         */
256        public static String parseText(CamelContext camelContext, String text) throws Exception {
257            // ensure we support property placeholders
258            return camelContext.resolvePropertyPlaceholders(text);
259        }
260    
261        /**
262         * Parses the given text and converts it to an Integer and handling property placeholders as well
263         *
264         * @param camelContext the camel context
265         * @param text  the text
266         * @return the integer vale, or <tt>null</tt> if the text was <tt>null</tt>
267         * @throws Exception is thrown if illegal argument or type conversion not possible
268         */
269        public static Integer parseInteger(CamelContext camelContext, String text) throws Exception {
270            // ensure we support property placeholders
271            String s = camelContext.resolvePropertyPlaceholders(text);
272            if (s != null) {
273                try {
274                    return camelContext.getTypeConverter().mandatoryConvertTo(Integer.class, s);
275                } catch (NumberFormatException e) {
276                    if (s.equals(text)) {
277                        throw new IllegalArgumentException("Error parsing [" + s + "] as an Integer.", e);
278                    } else {
279                        throw new IllegalArgumentException("Error parsing [" + s + "] from property " + text + " as an Integer.", e);
280                    }
281                }
282            }
283            return null;
284        }
285    
286        /**
287         * Parses the given text and converts it to an Long and handling property placeholders as well
288         *
289         * @param camelContext the camel context
290         * @param text  the text
291         * @return the long vale, or <tt>null</tt> if the text was <tt>null</tt>
292         * @throws Exception is thrown if illegal argument or type conversion not possible
293         */
294        public static Long parseLong(CamelContext camelContext, String text) throws Exception {
295            // ensure we support property placeholders
296            String s = camelContext.resolvePropertyPlaceholders(text);
297            if (s != null) {
298                try {
299                    return camelContext.getTypeConverter().mandatoryConvertTo(Long.class, s);
300                } catch (NumberFormatException e) {
301                    if (s.equals(text)) {
302                        throw new IllegalArgumentException("Error parsing [" + s + "] as a Long.", e);
303                    } else {
304                        throw new IllegalArgumentException("Error parsing [" + s + "] from property " + text + " as a Long.", e);
305                    }
306                }
307            }
308            return null;
309        }
310    
311        /**
312         * Parses the given text and converts it to a Double and handling property placeholders as well
313         *
314         * @param camelContext the camel context
315         * @param text  the text
316         * @return the double vale, or <tt>null</tt> if the text was <tt>null</tt>
317         * @throws Exception is thrown if illegal argument or type conversion not possible
318         */
319        public static Double parseDouble(CamelContext camelContext, String text) throws Exception {
320            // ensure we support property placeholders
321            String s = camelContext.resolvePropertyPlaceholders(text);
322            if (s != null) {
323                try {
324                    return camelContext.getTypeConverter().mandatoryConvertTo(Double.class, s);
325                } catch (NumberFormatException e) {
326                    if (s.equals(text)) {
327                        throw new IllegalArgumentException("Error parsing [" + s + "] as an Integer.", e);
328                    } else {
329                        throw new IllegalArgumentException("Error parsing [" + s + "] from property " + text + " as an Integer.", e);
330                    }
331                }
332            }
333            return null;
334        }
335    
336        /**
337         * Parses the given text and converts it to an Boolean and handling property placeholders as well
338         *
339         * @param camelContext the camel context
340         * @param text  the text
341         * @return the boolean vale, or <tt>null</tt> if the text was <tt>null</tt>
342         * @throws Exception is thrown if illegal argument or type conversion not possible
343         */
344        public static Boolean parseBoolean(CamelContext camelContext, String text) throws Exception {
345            // ensure we support property placeholders
346            String s = camelContext.resolvePropertyPlaceholders(text);
347            if (s != null) {
348                s = s.trim().toLowerCase(Locale.ENGLISH);
349                if (s.equals("true") || s.equals("false")) {
350                    return "true".equals(s) ? Boolean.TRUE : Boolean.FALSE;
351                } else {
352                    if (s.equals(text)) {
353                        throw new IllegalArgumentException("Error parsing [" + s + "] as a Boolean.");
354                    } else {
355                        throw new IllegalArgumentException("Error parsing [" + s + "] from property " + text + " as a Boolean.");
356                    }
357                }
358            }
359            return null;
360        }
361    
362        /**
363         * Finds all possible Components on the classpath, already registered in {@link org.apache.camel.CamelContext},
364         * and from the {@link org.apache.camel.spi.Registry}.
365         */
366        public static SortedMap<String, Properties> findComponents(CamelContext camelContext) throws LoadPropertiesException {
367            ClassResolver resolver = camelContext.getClassResolver();
368            LOG.debug("Finding all components using class resolver: {} -> {}", new Object[]{resolver});
369            Enumeration<URL> iter = resolver.loadAllResourcesAsURL(COMPONENT_DESCRIPTOR);
370            return findComponents(camelContext, iter);
371        }
372    
373        public static SortedMap<String, Properties> findComponents(CamelContext camelContext, Enumeration<URL> componentDescriptionIter)
374            throws LoadPropertiesException {
375    
376            SortedMap<String, Properties> map = new TreeMap<String, Properties>();
377            while (componentDescriptionIter != null && componentDescriptionIter.hasMoreElements()) {
378                URL url = componentDescriptionIter.nextElement();
379                LOG.trace("Finding components in url: {}", url);
380                try {
381                    Properties properties = new Properties();
382                    properties.load(url.openStream());
383                    String names = properties.getProperty("components");
384                    if (names != null) {
385                        StringTokenizer tok = new StringTokenizer(names);
386                        while (tok.hasMoreTokens()) {
387                            String name = tok.nextToken();
388    
389                            // try to find the class name for this component
390                            String className = null;
391                            InputStream is = null;
392                            try {
393                                // now load the component name resource so we can grab its properties and the class name
394                                Enumeration<URL> urls = camelContext.getClassResolver().loadAllResourcesAsURL(COMPONENT_BASE + name);
395                                if (urls != null && urls.hasMoreElements()) {
396                                    is = urls.nextElement().openStream();
397                                }
398                                if (is != null) {
399                                    Properties compProperties = new Properties();
400                                    compProperties.load(is);
401                                    if (!compProperties.isEmpty()) {
402                                        className = compProperties.getProperty("class");
403                                    }
404                                }
405                            } catch (Exception e) {
406                                // ignore
407                            } finally {
408                                IOHelper.close(is);
409                            }
410    
411                            // inherit properties we loaded first, as it has maven details
412                            Properties prop = new Properties();
413                            prop.putAll(properties);
414                            if (camelContext.hasComponent(name) != null) {
415                                prop.put("component", camelContext.getComponent(name));
416                            }
417                            if (className != null) {
418                                prop.put("class", className);
419                            }
420                            prop.put("name", name);
421                            map.put(name, prop);
422                        }
423                    }
424                } catch (IOException e) {
425                    throw new LoadPropertiesException(url, e);
426                }
427            }
428    
429            // lets see what other components are registered on camel context
430            List<String> names = camelContext.getComponentNames();
431            for (String name : names) {
432                if (!map.containsKey(name)) {
433                    Component component = camelContext.getComponent(name);
434                    if (component != null) {
435                        Properties properties = new Properties();
436                        properties.put("component", component);
437                        properties.put("class", component.getClass().getName());
438                        properties.put("name", name);
439                        map.put(name, properties);
440                    }
441                }
442            }
443    
444            // lets see what other components are in the registry
445            Map<String, Component> beanMap = camelContext.getRegistry().findByTypeWithName(Component.class);
446            Set<Map.Entry<String, Component>> entries = beanMap.entrySet();
447            for (Map.Entry<String, Component> entry : entries) {
448                String name = entry.getKey();
449                if (!map.containsKey(name)) {
450                    Component component = entry.getValue();
451                    if (component != null) {
452                        Properties properties = new Properties();
453                        properties.put("component", name);
454                        properties.put("class", component.getClass().getName());
455                        properties.put("name", name);
456                        map.put(name, properties);
457                    }
458                }
459            }
460            return map;
461        }
462    
463        /**
464         * Gets the route startup order for the given route id
465         *
466         * @param camelContext  the camel context
467         * @param routeId       the id of the route
468         * @return the startup order, or <tt>0</tt> if not possible to determine
469         */
470        public static int getRouteStartupOrder(CamelContext camelContext, String routeId) {
471            for (RouteStartupOrder order : camelContext.getRouteStartupOrder()) {
472                if (order.getRoute().getId().equals(routeId)) {
473                    return order.getStartupOrder();
474                }
475            }
476            return 0;
477        }
478    
479    }