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.HashMap; 022 import java.util.HashSet; 023 import java.util.List; 024 import java.util.Map; 025 import java.util.Set; 026 import java.util.concurrent.ConcurrentHashMap; 027 028 import org.apache.camel.CamelExecutionException; 029 import org.apache.camel.Exchange; 030 import org.apache.camel.NoFactoryAvailableException; 031 import org.apache.camel.NoTypeConversionAvailableException; 032 import org.apache.camel.TypeConverter; 033 import org.apache.camel.spi.FactoryFinder; 034 import org.apache.camel.spi.Injector; 035 import org.apache.camel.spi.PackageScanClassResolver; 036 import org.apache.camel.spi.TypeConverterAware; 037 import org.apache.camel.spi.TypeConverterRegistry; 038 import org.apache.camel.util.ObjectHelper; 039 import org.apache.commons.logging.Log; 040 import org.apache.commons.logging.LogFactory; 041 import static org.apache.camel.util.ObjectHelper.wrapRuntimeCamelException; 042 043 044 /** 045 * Default implementation of a type converter registry used for 046 * <a href="http://camel.apache.org/type-converter.html">type converters</a> in Camel. 047 * 048 * @version $Revision: 793935 $ 049 */ 050 public class DefaultTypeConverter implements TypeConverter, TypeConverterRegistry { 051 private static final transient Log LOG = LogFactory.getLog(DefaultTypeConverter.class); 052 private final Map<TypeMapping, TypeConverter> typeMappings = new ConcurrentHashMap<TypeMapping, TypeConverter>(); 053 private final Map<TypeMapping, TypeMapping> misses = new ConcurrentHashMap<TypeMapping, TypeMapping>(); 054 private final List<TypeConverterLoader> typeConverterLoaders = new ArrayList<TypeConverterLoader>(); 055 private final List<TypeConverter> fallbackConverters = new ArrayList<TypeConverter>(); 056 private Injector injector; 057 private final FactoryFinder factoryFinder; 058 private boolean loaded; 059 060 public DefaultTypeConverter(PackageScanClassResolver resolver, Injector injector, FactoryFinder factoryFinder) { 061 this.injector = injector; 062 this.factoryFinder = factoryFinder; 063 064 typeConverterLoaders.add(new AnnotationTypeConverterLoader(resolver)); 065 066 // add to string first as it will then be last in the last as to string can nearly 067 // always convert something to a string so we want it only as the last resort 068 addFallbackTypeConverter(new ToStringTypeConverter()); 069 addFallbackTypeConverter(new EnumTypeConverter()); 070 addFallbackTypeConverter(new ArrayTypeConverter()); 071 addFallbackTypeConverter(new PropertyEditorTypeConverter()); 072 addFallbackTypeConverter(new FutureTypeConverter(this)); 073 } 074 075 public List<TypeConverterLoader> getTypeConverterLoaders() { 076 return typeConverterLoaders; 077 } 078 079 public <T> T convertTo(Class<T> type, Object value) { 080 return convertTo(type, null, value); 081 } 082 083 public <T> T convertTo(Class<T> type, Exchange exchange, Object value) { 084 Object answer; 085 try { 086 answer = doConvertTo(type, exchange, value); 087 } catch (CamelExecutionException e) { 088 // rethrow exception exception as its not due to failed convertion 089 throw e; 090 } catch (Exception e) { 091 // we cannot convert so return null 092 if (LOG.isDebugEnabled()) { 093 LOG.debug(NoTypeConversionAvailableException.createMessage(value, type) 094 + " Caused by: " + e.getMessage() + ". Will ignore this and continue."); 095 } 096 return null; 097 } 098 if (answer == Void.TYPE) { 099 // Could not find suitable conversion 100 return null; 101 } else { 102 return (T) answer; 103 } 104 } 105 106 public <T> T mandatoryConvertTo(Class<T> type, Object value) throws NoTypeConversionAvailableException { 107 return mandatoryConvertTo(type, null, value); 108 } 109 110 public <T> T mandatoryConvertTo(Class<T> type, Exchange exchange, Object value) throws NoTypeConversionAvailableException { 111 Object answer; 112 try { 113 answer = doConvertTo(type, exchange, value); 114 } catch (Exception e) { 115 throw new NoTypeConversionAvailableException(value, type, e); 116 } 117 if (answer == Void.TYPE) { 118 // Could not find suitable conversion 119 throw new NoTypeConversionAvailableException(value, type); 120 } else { 121 return (T) answer; 122 } 123 } 124 125 @SuppressWarnings("unchecked") 126 public Object doConvertTo(final Class type, final Exchange exchange, final Object value) { 127 if (LOG.isTraceEnabled()) { 128 LOG.trace("Converting " + (value == null ? "null" : value.getClass().getCanonicalName()) 129 + " -> " + type.getCanonicalName() + " with value: " + value); 130 } 131 132 if (value == null) { 133 // lets avoid NullPointerException when converting to boolean for null values 134 if (boolean.class.isAssignableFrom(type)) { 135 return Boolean.FALSE; 136 } 137 return null; 138 } 139 140 // same instance type 141 if (type.isInstance(value)) { 142 return type.cast(value); 143 } 144 145 // check if we have tried it before and if its a miss 146 TypeMapping key = new TypeMapping(type, value.getClass()); 147 if (misses.containsKey(key)) { 148 // we have tried before but we cannot convert this one 149 return Void.TYPE; 150 } 151 152 // make sure we have loaded the converters 153 checkLoaded(); 154 155 // try to find a suitable type converter 156 TypeConverter converter = getOrFindTypeConverter(type, value); 157 if (converter != null) { 158 Object rc = converter.convertTo(type, exchange, value); 159 if (rc != null) { 160 return rc; 161 } 162 } 163 164 // fallback converters 165 for (TypeConverter fallback : fallbackConverters) { 166 Object rc = fallback.convertTo(type, exchange, value); 167 if (rc != null) { 168 // add it as a known type converter since we found a fallback that could do it 169 if (LOG.isDebugEnabled()) { 170 LOG.debug("Adding fallback type converter as a known type converter to convert from: " 171 + type.getCanonicalName() + " to: " + value.getClass().getCanonicalName()); 172 } 173 addTypeConverter(type, value.getClass(), fallback); 174 return rc; 175 } 176 } 177 178 // primitives 179 if (type.isPrimitive()) { 180 Class primitiveType = ObjectHelper.convertPrimitiveTypeToWrapperType(type); 181 if (primitiveType != type) { 182 return convertTo(primitiveType, exchange, value); 183 } 184 } 185 186 // Could not find suitable conversion, so remember it 187 synchronized (misses) { 188 misses.put(key, key); 189 } 190 191 // Could not find suitable conversion, so return Void to indicate not found 192 return Void.TYPE; 193 } 194 195 public void addTypeConverter(Class toType, Class fromType, TypeConverter typeConverter) { 196 if (LOG.isTraceEnabled()) { 197 LOG.trace("Adding type converter: " + typeConverter); 198 } 199 TypeMapping key = new TypeMapping(toType, fromType); 200 synchronized (typeMappings) { 201 TypeConverter converter = typeMappings.get(key); 202 if (converter != null) { 203 LOG.warn("Overriding type converter from: " + converter + " to: " + typeConverter); 204 } 205 typeMappings.put(key, typeConverter); 206 } 207 } 208 209 public void addFallbackTypeConverter(TypeConverter typeConverter) { 210 if (LOG.isTraceEnabled()) { 211 LOG.trace("Adding fallback type converter: " + typeConverter); 212 } 213 214 // add in top of fallback as the toString() fallback will nearly always be able to convert 215 fallbackConverters.add(0, typeConverter); 216 if (typeConverter instanceof TypeConverterAware) { 217 TypeConverterAware typeConverterAware = (TypeConverterAware)typeConverter; 218 typeConverterAware.setTypeConverter(this); 219 } 220 } 221 222 public TypeConverter getTypeConverter(Class toType, Class fromType) { 223 TypeMapping key = new TypeMapping(toType, fromType); 224 return typeMappings.get(key); 225 } 226 227 public Injector getInjector() { 228 return injector; 229 } 230 231 public void setInjector(Injector injector) { 232 this.injector = injector; 233 } 234 235 public Set<Class> getFromClassMappings() { 236 // make sure we have loaded the converters 237 checkLoaded(); 238 239 Set<Class> answer = new HashSet<Class>(); 240 synchronized (typeMappings) { 241 for (TypeMapping mapping : typeMappings.keySet()) { 242 answer.add(mapping.getFromType()); 243 } 244 } 245 return answer; 246 } 247 248 public Map<Class, TypeConverter> getToClassMappings(Class fromClass) { 249 Map<Class, TypeConverter> answer = new HashMap<Class, TypeConverter>(); 250 synchronized (typeMappings) { 251 for (Map.Entry<TypeMapping, TypeConverter> entry : typeMappings.entrySet()) { 252 TypeMapping mapping = entry.getKey(); 253 if (mapping.isApplicable(fromClass)) { 254 answer.put(mapping.getToType(), entry.getValue()); 255 } 256 } 257 } 258 return answer; 259 } 260 261 public Map<TypeMapping, TypeConverter> getTypeMappings() { 262 // make sure we have loaded the converters 263 checkLoaded(); 264 265 return typeMappings; 266 } 267 268 protected <T> TypeConverter getOrFindTypeConverter(Class toType, Object value) { 269 Class fromType = null; 270 if (value != null) { 271 fromType = value.getClass(); 272 } 273 TypeMapping key = new TypeMapping(toType, fromType); 274 TypeConverter converter; 275 synchronized (typeMappings) { 276 converter = typeMappings.get(key); 277 if (converter == null) { 278 converter = lookup(toType, fromType); 279 if (converter != null) { 280 typeMappings.put(key, converter); 281 } 282 } 283 } 284 return converter; 285 } 286 287 public TypeConverter lookup(Class toType, Class fromType) { 288 // make sure we have loaded the converters 289 checkLoaded(); 290 291 return doLookup(toType, fromType, false); 292 } 293 294 private TypeConverter doLookup(Class toType, Class fromType, boolean isSuper) { 295 296 if (fromType != null) { 297 // lets try if there is a direct match 298 TypeConverter converter = getTypeConverter(toType, fromType); 299 if (converter != null) { 300 return converter; 301 } 302 303 // try the interfaces 304 for (Class type : fromType.getInterfaces()) { 305 converter = getTypeConverter(toType, type); 306 if (converter != null) { 307 return converter; 308 } 309 } 310 311 // try super then 312 Class fromSuperClass = fromType.getSuperclass(); 313 if (fromSuperClass != null && !fromSuperClass.equals(Object.class)) { 314 converter = doLookup(toType, fromSuperClass, true); 315 if (converter != null) { 316 return converter; 317 } 318 } 319 } 320 321 // only do these tests as fallback and only on the target type (eg not on its super) 322 if (!isSuper) { 323 if (fromType != null && !fromType.equals(Object.class)) { 324 325 // lets try classes derived from this toType 326 Set<Map.Entry<TypeMapping, TypeConverter>> entries = typeMappings.entrySet(); 327 for (Map.Entry<TypeMapping, TypeConverter> entry : entries) { 328 TypeMapping key = entry.getKey(); 329 Class aToType = key.getToType(); 330 if (toType.isAssignableFrom(aToType)) { 331 Class aFromType = key.getFromType(); 332 // skip Object based we do them last 333 if (!aFromType.equals(Object.class) && aFromType.isAssignableFrom(fromType)) { 334 return entry.getValue(); 335 } 336 } 337 } 338 339 // lets test for Object based converters as last resort 340 TypeConverter converter = getTypeConverter(toType, Object.class); 341 if (converter != null) { 342 return converter; 343 } 344 } 345 } 346 347 // none found 348 return null; 349 } 350 351 /** 352 * Checks if the registry is loaded and if not lazily load it 353 */ 354 protected synchronized void checkLoaded() { 355 if (!loaded) { 356 loaded = true; 357 try { 358 for (TypeConverterLoader typeConverterLoader : typeConverterLoaders) { 359 typeConverterLoader.load(this); 360 } 361 362 // lets try load any other fallback converters 363 try { 364 loadFallbackTypeConverters(); 365 } catch (NoFactoryAvailableException e) { 366 // ignore its fine to have none 367 } 368 } catch (Exception e) { 369 throw wrapRuntimeCamelException(e); 370 } 371 } 372 } 373 374 protected void loadFallbackTypeConverters() throws IOException, ClassNotFoundException { 375 List<TypeConverter> converters = factoryFinder.newInstances("FallbackTypeConverter", getInjector(), TypeConverter.class); 376 for (TypeConverter converter : converters) { 377 addFallbackTypeConverter(converter); 378 } 379 } 380 381 /** 382 * Represents a mapping from one type (which can be null) to another 383 */ 384 protected static class TypeMapping { 385 Class toType; 386 Class fromType; 387 388 public TypeMapping(Class toType, Class fromType) { 389 this.toType = toType; 390 this.fromType = fromType; 391 } 392 393 public Class getFromType() { 394 return fromType; 395 } 396 397 public Class getToType() { 398 return toType; 399 } 400 401 @Override 402 public boolean equals(Object object) { 403 if (object instanceof TypeMapping) { 404 TypeMapping that = (TypeMapping)object; 405 return ObjectHelper.equal(this.fromType, that.fromType) 406 && ObjectHelper.equal(this.toType, that.toType); 407 } 408 return false; 409 } 410 411 @Override 412 public int hashCode() { 413 int answer = toType.hashCode(); 414 if (fromType != null) { 415 answer *= 37 + fromType.hashCode(); 416 } 417 return answer; 418 } 419 420 @Override 421 public String toString() { 422 return "[" + fromType + "=>" + toType + "]"; 423 } 424 425 public boolean isApplicable(Class fromClass) { 426 return fromType.isAssignableFrom(fromClass); 427 } 428 } 429 }