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.impl.converter; 018 019 import java.io.BufferedReader; 020 import java.io.IOException; 021 import java.io.InputStreamReader; 022 import java.lang.reflect.Method; 023 024 import java.net.URL; 025 import java.util.Enumeration; 026 import java.util.HashSet; 027 import java.util.Set; 028 import java.util.StringTokenizer; 029 import static java.lang.reflect.Modifier.isAbstract; 030 import static java.lang.reflect.Modifier.isPublic; 031 import static java.lang.reflect.Modifier.isStatic; 032 033 import org.apache.camel.Converter; 034 import org.apache.camel.Exchange; 035 import org.apache.camel.FallbackConverter; 036 import org.apache.camel.TypeConverter; 037 import org.apache.camel.spi.PackageScanClassResolver; 038 import org.apache.camel.spi.TypeConverterRegistry; 039 import org.apache.camel.util.ObjectHelper; 040 import org.apache.commons.logging.Log; 041 import org.apache.commons.logging.LogFactory; 042 043 /** 044 * A class which will auto-discover converter objects and methods to pre-load 045 * the registry of converters on startup 046 * 047 * @version $Revision: 749648 $ 048 */ 049 public class AnnotationTypeConverterLoader implements TypeConverterLoader { 050 public static final String META_INF_SERVICES = "META-INF/services/org/apache/camel/TypeConverter"; 051 private static final transient Log LOG = LogFactory.getLog(AnnotationTypeConverterLoader.class); 052 protected PackageScanClassResolver resolver; 053 private Set<Class> visitedClasses = new HashSet<Class>(); 054 055 public AnnotationTypeConverterLoader(PackageScanClassResolver resolver) { 056 this.resolver = resolver; 057 } 058 059 @SuppressWarnings("unchecked") 060 public void load(TypeConverterRegistry registry) throws Exception { 061 String[] packageNames = findPackageNames(); 062 Set<Class> classes = resolver.findAnnotated(Converter.class, packageNames); 063 for (Class type : classes) { 064 if (LOG.isDebugEnabled()) { 065 LOG.debug("Loading converter class: " + ObjectHelper.name(type)); 066 } 067 loadConverterMethods(registry, type); 068 } 069 } 070 071 /** 072 * Finds the names of the packages to search for on the classpath looking 073 * for text files on the classpath at the {@link #META_INF_SERVICES} location. 074 * 075 * @return a collection of packages to search for 076 * @throws IOException is thrown for IO related errors 077 */ 078 protected String[] findPackageNames() throws IOException { 079 Set<String> packages = new HashSet<String>(); 080 ClassLoader ccl = Thread.currentThread().getContextClassLoader(); 081 if (ccl != null) { 082 findPackages(packages, ccl); 083 } 084 findPackages(packages, getClass().getClassLoader()); 085 return packages.toArray(new String[packages.size()]); 086 } 087 088 protected void findPackages(Set<String> packages, ClassLoader classLoader) throws IOException { 089 Enumeration<URL> resources = classLoader.getResources(META_INF_SERVICES); 090 while (resources.hasMoreElements()) { 091 URL url = resources.nextElement(); 092 if (url != null) { 093 BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream())); 094 try { 095 while (true) { 096 String line = reader.readLine(); 097 if (line == null) { 098 break; 099 } 100 line = line.trim(); 101 if (line.startsWith("#") || line.length() == 0) { 102 continue; 103 } 104 tokenize(packages, line); 105 } 106 } finally { 107 ObjectHelper.close(reader, null, LOG); 108 } 109 } 110 } 111 } 112 113 /** 114 * Tokenizes the line from the META-IN/services file using commas and 115 * ignoring whitespace between packages 116 */ 117 private void tokenize(Set<String> packages, String line) { 118 StringTokenizer iter = new StringTokenizer(line, ","); 119 while (iter.hasMoreTokens()) { 120 String name = iter.nextToken().trim(); 121 if (name.length() > 0) { 122 packages.add(name); 123 } 124 } 125 } 126 127 /** 128 * Loads all of the converter methods for the given type 129 */ 130 protected void loadConverterMethods(TypeConverterRegistry registry, Class type) { 131 if (visitedClasses.contains(type)) { 132 return; 133 } 134 visitedClasses.add(type); 135 try { 136 Method[] methods = type.getDeclaredMethods(); 137 CachingInjector injector = null; 138 139 for (Method method : methods) { 140 // this may be prone to ClassLoader or packaging problems when the same class is defined 141 // in two different jars (as is the case sometimes with specs). 142 if (ObjectHelper.hasAnnotation(method, Converter.class, true)) { 143 injector = handleHasConverterAnnotation(registry, type, injector, method); 144 } else if (ObjectHelper.hasAnnotation(method, FallbackConverter.class, true)) { 145 injector = handleHasFallbackConverterAnnotation(registry, type, injector, method); 146 } 147 } 148 149 Class superclass = type.getSuperclass(); 150 if (superclass != null && !superclass.equals(Object.class)) { 151 loadConverterMethods(registry, superclass); 152 } 153 } catch (NoClassDefFoundError e) { 154 LOG.warn("Ignoring converter type: " + type.getCanonicalName() + " as a dependent class could not be found: " + e, e); 155 } 156 } 157 158 @SuppressWarnings("unchecked") 159 private CachingInjector handleHasConverterAnnotation(TypeConverterRegistry registry, Class type, CachingInjector injector, Method method) { 160 if (isValidConverterMethod(method)) { 161 int modifiers = method.getModifiers(); 162 if (isAbstract(modifiers) || !isPublic(modifiers)) { 163 LOG.warn("Ignoring bad converter on type: " + type.getCanonicalName() + " method: " + method 164 + " as a converter method is not a public and concrete method"); 165 } else { 166 Class<?> toType = method.getReturnType(); 167 if (toType.equals(Void.class)) { 168 LOG.warn("Ignoring bad converter on type: " + type.getCanonicalName() + " method: " 169 + method + " as a converter method returns a void method"); 170 } else { 171 Class<?> fromType = method.getParameterTypes()[0]; 172 if (isStatic(modifiers)) { 173 registerTypeConverter(registry, method, toType, fromType, 174 new StaticMethodTypeConverter(method)); 175 } else { 176 if (injector == null) { 177 injector = new CachingInjector(registry, type); 178 } 179 registerTypeConverter(registry, method, toType, fromType, 180 new InstanceMethodTypeConverter(injector, method)); 181 } 182 } 183 } 184 } else { 185 LOG.warn("Ignoring bad converter on type: " + type.getCanonicalName() + " method: " + method 186 + " as a converter method should have one parameter"); 187 } 188 return injector; 189 } 190 191 @SuppressWarnings("unchecked") 192 private CachingInjector handleHasFallbackConverterAnnotation(TypeConverterRegistry registry, Class type, CachingInjector injector, Method method) { 193 if (isValidFallbackConverterMethod(method)) { 194 int modifiers = method.getModifiers(); 195 if (isAbstract(modifiers) || !isPublic(modifiers)) { 196 LOG.warn("Ignoring bad fallback converter on type: " + type.getCanonicalName() + " method: " + method 197 + " as a fallback converter method is not a public and concrete method"); 198 } else { 199 Class<?> toType = method.getReturnType(); 200 if (toType.equals(Void.class)) { 201 LOG.warn("Ignoring bad fallback converter on type: " + type.getCanonicalName() + " method: " 202 + method + " as a fallback converter method returns a void method"); 203 } else { 204 if (isStatic(modifiers)) { 205 registerFallbackTypeConverter(registry, new StaticMethodFallbackTypeConverter(method, registry)); 206 } else { 207 if (injector == null) { 208 injector = new CachingInjector(registry, type); 209 } 210 registerFallbackTypeConverter(registry, new InstanceMethodFallbackTypeConverter(injector, method, registry)); 211 } 212 } 213 } 214 } else { 215 LOG.warn("Ignoring bad fallback converter on type: " + type.getCanonicalName() + " method: " + method 216 + " as a fallback converter method should have one parameter"); 217 } 218 return injector; 219 } 220 221 protected void registerTypeConverter(TypeConverterRegistry registry, 222 Method method, Class toType, Class fromType, TypeConverter typeConverter) { 223 registry.addTypeConverter(toType, fromType, typeConverter); 224 } 225 226 protected boolean isValidConverterMethod(Method method) { 227 Class<?>[] parameterTypes = method.getParameterTypes(); 228 return (parameterTypes != null) && (parameterTypes.length == 1 229 || (parameterTypes.length == 2 && Exchange.class.isAssignableFrom(parameterTypes[1]))); 230 } 231 232 protected void registerFallbackTypeConverter(TypeConverterRegistry registry, TypeConverter typeConverter) { 233 registry.addFallbackTypeConverter(typeConverter); 234 } 235 236 protected boolean isValidFallbackConverterMethod(Method method) { 237 Class<?>[] parameterTypes = method.getParameterTypes(); 238 return (parameterTypes != null) && (parameterTypes.length == 3 239 || (parameterTypes.length == 4 && Exchange.class.isAssignableFrom(parameterTypes[1])) 240 && (TypeConverterRegistry.class.isAssignableFrom(parameterTypes[parameterTypes.length - 1]))); 241 } 242 }