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 }