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.IOException; 020 import java.util.ArrayList; 021 import java.util.List; 022 import java.util.Map; 023 import java.util.Set; 024 import java.util.concurrent.ConcurrentHashMap; 025 026 import org.apache.camel.Exchange; 027 import org.apache.camel.RuntimeCamelException; 028 import org.apache.camel.TypeConverter; 029 import org.apache.camel.spi.Injector; 030 import org.apache.camel.spi.TypeConverterAware; 031 import org.apache.camel.util.FactoryFinder; 032 import org.apache.camel.util.NoFactoryAvailableException; 033 import org.apache.camel.util.ObjectHelper; 034 import org.apache.commons.logging.Log; 035 import org.apache.commons.logging.LogFactory; 036 037 /** 038 * Default implementation of a type converter registry used for 039 * <a href="http://activemq.apache.org/camel/type-converter.html">type converters</a> in Camel. 040 * 041 * @version $Revision: 47150 $ 042 */ 043 public class DefaultTypeConverter implements TypeConverter, TypeConverterRegistry { 044 private static final transient Log LOG = LogFactory.getLog(DefaultTypeConverter.class); 045 private final Map<TypeMapping, TypeConverter> typeMappings = new ConcurrentHashMap<TypeMapping, TypeConverter>(); 046 private Injector injector; 047 private List<TypeConverterLoader> typeConverterLoaders = new ArrayList<TypeConverterLoader>(); 048 private List<TypeConverter> fallbackConverters = new ArrayList<TypeConverter>(); 049 private boolean loaded; 050 051 public DefaultTypeConverter(Injector injector) { 052 typeConverterLoaders.add(new AnnotationTypeConverterLoader()); 053 this.injector = injector; 054 addFallbackConverter(new AsyncProcessorTypeConverter()); 055 addFallbackConverter(new PropertyEditorTypeConverter()); 056 addFallbackConverter(new ToStringTypeConverter()); 057 addFallbackConverter(new ArrayTypeConverter()); 058 addFallbackConverter(new EnumTypeConverter()); 059 } 060 061 public <T> T convertTo(Class<T> type, Object value) { 062 return convertTo(type, null, value); 063 } 064 065 @SuppressWarnings("unchecked") 066 public <T> T convertTo(Class<T> type, Exchange exchange, Object value) { 067 if (LOG.isTraceEnabled()) { 068 LOG.trace("Converting " + (value == null ? "null" : value.getClass().getCanonicalName()) 069 + " -> " + type.getCanonicalName() + " with value: " + value); 070 } 071 072 // same instance type 073 if (type.isInstance(value)) { 074 return type.cast(value); 075 } 076 077 // make sure we have loaded the converters 078 checkLoaded(); 079 080 // try to find a suitable type converter 081 TypeConverter converter = getOrFindTypeConverter(type, value); 082 if (converter != null) { 083 return converter.convertTo(type, exchange, value); 084 } 085 086 // fallback converters 087 for (TypeConverter fallback : fallbackConverters) { 088 T rc = fallback.convertTo(type, exchange, value); 089 if (rc != null) { 090 return rc; 091 } 092 } 093 094 // lets avoid NullPointerException when converting to boolean for null values 095 if (boolean.class.isAssignableFrom(type)) { 096 return (T) Boolean.FALSE; 097 } 098 099 // primitives 100 if (type.isPrimitive()) { 101 Class primitiveType = ObjectHelper.convertPrimitiveTypeToWrapperType(type); 102 if (primitiveType != type) { 103 return (T) convertTo(primitiveType, exchange, value); 104 } 105 } 106 107 boolean camelType = type.getCanonicalName().startsWith("org.apache.camel"); 108 if (!camelType && value != null) { 109 // only log WARN level for non internal Camel convertions 110 LOG.warn("Could not find a type converter for converting " 111 + value.getClass().getCanonicalName() + " -> " 112 + type.getCanonicalName() + " with value: " + value); 113 } 114 return null; 115 } 116 117 public void addTypeConverter(Class toType, Class fromType, TypeConverter typeConverter) { 118 TypeMapping key = new TypeMapping(toType, fromType); 119 synchronized (typeMappings) { 120 TypeConverter converter = typeMappings.get(key); 121 if (converter != null) { 122 LOG.warn("Overriding type converter from: " + converter + " to: " + typeConverter); 123 } 124 typeMappings.put(key, typeConverter); 125 } 126 } 127 128 public void addFallbackConverter(TypeConverter converter) { 129 fallbackConverters.add(converter); 130 if (converter instanceof TypeConverterAware) { 131 TypeConverterAware typeConverterAware = (TypeConverterAware)converter; 132 typeConverterAware.setTypeConverter(this); 133 } 134 } 135 136 public TypeConverter getTypeConverter(Class toType, Class fromType) { 137 TypeMapping key = new TypeMapping(toType, fromType); 138 return typeMappings.get(key); 139 } 140 141 public Injector getInjector() { 142 return injector; 143 } 144 145 public void setInjector(Injector injector) { 146 this.injector = injector; 147 } 148 149 protected <T> TypeConverter getOrFindTypeConverter(Class toType, Object value) { 150 Class fromType = null; 151 if (value != null) { 152 fromType = value.getClass(); 153 } 154 TypeMapping key = new TypeMapping(toType, fromType); 155 TypeConverter converter; 156 synchronized (typeMappings) { 157 converter = typeMappings.get(key); 158 if (converter == null) { 159 converter = findTypeConverter(toType, fromType, value); 160 if (converter != null) { 161 typeMappings.put(key, converter); 162 } 163 } 164 } 165 return converter; 166 } 167 168 /** 169 * Tries to auto-discover any available type converters 170 */ 171 protected TypeConverter findTypeConverter(Class toType, Class fromType, Object value) { 172 // lets try the super classes of the from type 173 if (fromType != null) { 174 Class fromSuperClass = fromType.getSuperclass(); 175 if (fromSuperClass != null && !fromSuperClass.equals(Object.class)) { 176 177 TypeConverter converter = getTypeConverter(toType, fromSuperClass); 178 if (converter == null) { 179 converter = findTypeConverter(toType, fromSuperClass, value); 180 } 181 if (converter != null) { 182 return converter; 183 } 184 } 185 for (Class type : fromType.getInterfaces()) { 186 TypeConverter converter = getTypeConverter(toType, type); 187 if (converter != null) { 188 return converter; 189 } 190 } 191 192 // lets test for arrays 193 if (fromType.isArray() && !fromType.getComponentType().isPrimitive()) { 194 // TODO can we try walking the inheritance-tree for the element types? 195 if (!fromType.equals(Object[].class)) { 196 fromSuperClass = Object[].class; 197 198 TypeConverter converter = getTypeConverter(toType, fromSuperClass); 199 if (converter == null) { 200 converter = findTypeConverter(toType, fromSuperClass, value); 201 } 202 if (converter != null) { 203 return converter; 204 } 205 } 206 } 207 208 // lets test for Object based converters 209 if (!fromType.equals(Object.class)) { 210 TypeConverter converter = getTypeConverter(toType, Object.class); 211 if (converter != null) { 212 return converter; 213 } 214 } 215 } 216 217 // lets try classes derived from this toType 218 if (fromType != null) { 219 Set<Map.Entry<TypeMapping, TypeConverter>> entries = typeMappings.entrySet(); 220 for (Map.Entry<TypeMapping, TypeConverter> entry : entries) { 221 TypeMapping key = entry.getKey(); 222 Class aToType = key.getToType(); 223 if (toType.isAssignableFrom(aToType)) { 224 if (key.getFromType().isAssignableFrom(fromType)) { 225 return entry.getValue(); 226 } 227 } 228 } 229 } 230 231 // TODO look at constructors of toType? 232 return null; 233 } 234 235 /** 236 * Checks if the registry is loaded and if not lazily load it 237 */ 238 protected synchronized void checkLoaded() { 239 if (!loaded) { 240 loaded = true; 241 try { 242 for (TypeConverterLoader typeConverterLoader : typeConverterLoaders) { 243 typeConverterLoader.load(this); 244 } 245 246 // lets try load any other fallback converters 247 try { 248 loadFallbackTypeConverters(); 249 } catch (NoFactoryAvailableException e) { 250 // ignore its fine to have none 251 } 252 } catch (Exception e) { 253 throw new RuntimeCamelException(e); 254 } 255 } 256 } 257 258 protected void loadFallbackTypeConverters() throws IOException, ClassNotFoundException { 259 FactoryFinder finder = new FactoryFinder(); 260 List<TypeConverter> converters = finder.newInstances("FallbackTypeConverter", getInjector(), 261 TypeConverter.class); 262 for (TypeConverter converter : converters) { 263 addFallbackConverter(converter); 264 } 265 } 266 267 /** 268 * Represents a mapping from one type (which can be null) to another 269 */ 270 protected static class TypeMapping { 271 Class toType; 272 Class fromType; 273 274 public TypeMapping(Class toType, Class fromType) { 275 this.toType = toType; 276 this.fromType = fromType; 277 } 278 279 public Class getFromType() { 280 return fromType; 281 } 282 283 public Class getToType() { 284 return toType; 285 } 286 287 @Override 288 public boolean equals(Object object) { 289 if (object instanceof TypeMapping) { 290 TypeMapping that = (TypeMapping)object; 291 return ObjectHelper.equal(this.fromType, that.fromType) 292 && ObjectHelper.equal(this.toType, that.toType); 293 } 294 return false; 295 } 296 297 @Override 298 public int hashCode() { 299 int answer = toType.hashCode(); 300 if (fromType != null) { 301 answer *= 37 + fromType.hashCode(); 302 } 303 return answer; 304 } 305 306 @Override 307 public String toString() { 308 return "[" + fromType + "=>" + toType + "]"; 309 } 310 } 311 }