001    /**
002     *
003     * Licensed to the Apache Software Foundation (ASF) under one or more
004     * contributor license agreements.  See the NOTICE file distributed with
005     * this work for additional information regarding copyright ownership.
006     * The ASF licenses this file to You under the Apache License, Version 2.0
007     * (the "License"); you may not use this file except in compliance with
008     * the License.  You may obtain a copy of the License at
009     *
010     * http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    package org.apache.camel.impl.converter;
019    
020    import org.apache.camel.Converter;
021    import org.apache.camel.impl.CachingInjector;
022    import org.apache.camel.util.ResolverUtil;
023    import org.apache.camel.util.ObjectHelper;
024    import org.apache.commons.logging.Log;
025    import org.apache.commons.logging.LogFactory;
026    
027    import java.io.BufferedReader;
028    import java.io.IOException;
029    import java.io.InputStreamReader;
030    import java.lang.reflect.Method;
031    import static java.lang.reflect.Modifier.*;
032    import java.net.URL;
033    import java.util.Enumeration;
034    import java.util.HashSet;
035    import java.util.Set;
036    import java.util.StringTokenizer;
037    
038    /**
039     * A class which will auto-discover converter objects and methods to pre-load
040     * the registry of converters on startup
041     *
042     * @version $Revision: 546860 $
043     */
044    public class AnnotationTypeConverterLoader implements TypeConverterLoader {
045        public static final String META_INF_SERVICES = "META-INF/services/org/apache/camel/TypeConverter";
046        private static final transient Log log = LogFactory.getLog(AnnotationTypeConverterLoader.class);
047        private ResolverUtil resolver = new ResolverUtil();
048        private Set<Class> visitedClasses = new HashSet<Class>();
049    
050        public void load(TypeConverterRegistry registry) throws Exception {
051            String[] packageNames = findPackageNames();
052            resolver.findAnnotated(Converter.class, packageNames);
053            Set<Class> classes = resolver.getClasses();
054            for (Class type : classes) {
055                if (log.isDebugEnabled()) {
056                    log.debug("Loading converter class: " + ObjectHelper.name(type));
057                }
058                loadConverterMethods(registry, type);
059            }
060        }
061    
062        /**
063         * Finds the names of the packages to search for on the classpath looking for text files on the classpath
064         * at the  @{link #META_INF_SERVICES} location
065         *
066         * @return a collection of packages to search for
067         * @throws IOException
068         */
069        protected String[] findPackageNames() throws IOException {
070            Set<String> packages = new HashSet<String>();
071            findPackages(packages, Thread.currentThread().getContextClassLoader());
072            findPackages(packages, getClass().getClassLoader());
073            return packages.toArray(new String[packages.size()]);
074        }
075    
076        protected void findPackages(Set<String> packages, ClassLoader classLoader) throws IOException {
077            Enumeration<URL> resources = classLoader.getResources(META_INF_SERVICES);
078            while (resources.hasMoreElements()) {
079                URL url = resources.nextElement();
080                if (url != null) {
081                    BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));
082                    try {
083                        while (true) {
084                            String line = reader.readLine();
085                            if (line == null) {
086                                break;
087                            }
088                            line = line.trim();
089                            if (line.startsWith("#") || line.length() == 0) {
090                                continue;
091                            }
092                            tokenize(packages, line);
093                        }
094                    }
095                    finally {
096                        try {
097                            reader.close();
098                        }
099                        catch (IOException e) {
100                            log.warn("Caught exception closing stream: " + e, e);
101                        }
102                    }
103                }
104            }
105        }
106    
107        /**
108         * Tokenizes the line from the META-IN/services file using commas and ignoring whitespace between packages
109         */
110        protected void tokenize(Set<String> packages, String line) {
111            StringTokenizer iter = new StringTokenizer(line, ",");
112            while (iter.hasMoreTokens()) {
113                String name = iter.nextToken().trim();
114                if (name.length() > 0) {
115                    packages.add(name);
116                }
117            }
118        }
119    
120        /**
121         * Loads all of the converter methods for the given type
122         */
123        protected void loadConverterMethods(TypeConverterRegistry registry, Class type) {
124            if (visitedClasses.contains(type)) {
125                return;
126            }
127            visitedClasses.add(type);
128            Method[] methods = type.getDeclaredMethods();
129            CachingInjector injector = null;
130    
131            for (Method method : methods) {
132                Converter annotation = method.getAnnotation(Converter.class);
133                if (annotation != null) {
134                    Class<?>[] parameterTypes = method.getParameterTypes();
135                    if (parameterTypes == null || parameterTypes.length != 1) {
136                        log.warn("Ignoring bad converter on type: " + type.getName()
137                                + " method: " + method + " as a converter method should have one parameter");
138                    }
139                    else {
140                        int modifiers = method.getModifiers();
141                        if (isAbstract(modifiers) || !isPublic(modifiers)) {
142                            log.warn("Ignoring bad converter on type: " + type.getName()
143                                    + " method: " + method + " as a converter method is not a public and concrete method");
144                        }
145                        else {
146                            Class toType = method.getReturnType();
147                            if (toType.equals(Void.class)) {
148                                log.warn("Ignoring bad converter on type: " + type.getName()
149                                        + " method: " + method + " as a converter method returns a void method");
150                            }
151                            else {
152                                Class fromType = parameterTypes[0];
153                                if (isStatic(modifiers)) {
154                                    registry.addTypeConverter(toType, fromType, new StaticMethodTypeConverter(method));
155                                }
156                                else {
157                                    if (injector == null) {
158                                        injector = new CachingInjector(registry, type);
159                                    }
160                                    registry.addTypeConverter(toType, fromType, new InstanceMethodTypeConverter(injector, method));
161                                }
162                            }
163                        }
164                    }
165                }
166            }
167            Class superclass = type.getSuperclass();
168            if (superclass != null && !superclass.equals(Object.class)) {
169                loadConverterMethods(registry, superclass);
170            }
171        }
172    }