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: 772598 $ 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 return rc; 169 } 170 } 171 172 // primitives 173 if (type.isPrimitive()) { 174 Class primitiveType = ObjectHelper.convertPrimitiveTypeToWrapperType(type); 175 if (primitiveType != type) { 176 return convertTo(primitiveType, exchange, value); 177 } 178 } 179 180 // Could not find suitable conversion, so remember it 181 synchronized (misses) { 182 misses.put(key, key); 183 } 184 185 // Could not find suitable conversion, so return Void to indicate not found 186 return Void.TYPE; 187 } 188 189 public void addTypeConverter(Class toType, Class fromType, TypeConverter typeConverter) { 190 if (LOG.isTraceEnabled()) { 191 LOG.trace("Adding type converter: " + typeConverter); 192 } 193 TypeMapping key = new TypeMapping(toType, fromType); 194 synchronized (typeMappings) { 195 TypeConverter converter = typeMappings.get(key); 196 if (converter != null) { 197 LOG.warn("Overriding type converter from: " + converter + " to: " + typeConverter); 198 } 199 typeMappings.put(key, typeConverter); 200 } 201 } 202 203 public void addFallbackTypeConverter(TypeConverter typeConverter) { 204 if (LOG.isTraceEnabled()) { 205 LOG.trace("Adding fallback type converter: " + typeConverter); 206 } 207 208 // add in top of fallback as the toString() fallback will nearly always be able to convert 209 fallbackConverters.add(0, typeConverter); 210 if (typeConverter instanceof TypeConverterAware) { 211 TypeConverterAware typeConverterAware = (TypeConverterAware)typeConverter; 212 typeConverterAware.setTypeConverter(this); 213 } 214 } 215 216 public TypeConverter getTypeConverter(Class toType, Class fromType) { 217 TypeMapping key = new TypeMapping(toType, fromType); 218 return typeMappings.get(key); 219 } 220 221 public Injector getInjector() { 222 return injector; 223 } 224 225 public void setInjector(Injector injector) { 226 this.injector = injector; 227 } 228 229 public Set<Class> getFromClassMappings() { 230 // make sure we have loaded the converters 231 checkLoaded(); 232 233 Set<Class> answer = new HashSet<Class>(); 234 synchronized (typeMappings) { 235 for (TypeMapping mapping : typeMappings.keySet()) { 236 answer.add(mapping.getFromType()); 237 } 238 } 239 return answer; 240 } 241 242 public Map<Class, TypeConverter> getToClassMappings(Class fromClass) { 243 Map<Class, TypeConverter> answer = new HashMap<Class, TypeConverter>(); 244 synchronized (typeMappings) { 245 for (Map.Entry<TypeMapping, TypeConverter> entry : typeMappings.entrySet()) { 246 TypeMapping mapping = entry.getKey(); 247 if (mapping.isApplicable(fromClass)) { 248 answer.put(mapping.getToType(), entry.getValue()); 249 } 250 } 251 } 252 return answer; 253 } 254 255 public Map<TypeMapping, TypeConverter> getTypeMappings() { 256 // make sure we have loaded the converters 257 checkLoaded(); 258 259 return typeMappings; 260 } 261 262 protected <T> TypeConverter getOrFindTypeConverter(Class toType, Object value) { 263 Class fromType = null; 264 if (value != null) { 265 fromType = value.getClass(); 266 } 267 TypeMapping key = new TypeMapping(toType, fromType); 268 TypeConverter converter; 269 synchronized (typeMappings) { 270 converter = typeMappings.get(key); 271 if (converter == null) { 272 converter = lookup(toType, fromType); 273 if (converter != null) { 274 typeMappings.put(key, converter); 275 } 276 } 277 } 278 return converter; 279 } 280 281 public TypeConverter lookup(Class toType, Class fromType) { 282 // make sure we have loaded the converters 283 checkLoaded(); 284 285 return doLookup(toType, fromType, false); 286 } 287 288 private TypeConverter doLookup(Class toType, Class fromType, boolean isSuper) { 289 290 if (fromType != null) { 291 // lets try if there is a direct match 292 TypeConverter converter = getTypeConverter(toType, fromType); 293 if (converter != null) { 294 return converter; 295 } 296 297 // try the interfaces 298 for (Class type : fromType.getInterfaces()) { 299 converter = getTypeConverter(toType, type); 300 if (converter != null) { 301 return converter; 302 } 303 } 304 305 // try super then 306 Class fromSuperClass = fromType.getSuperclass(); 307 if (fromSuperClass != null && !fromSuperClass.equals(Object.class)) { 308 converter = doLookup(toType, fromSuperClass, true); 309 if (converter != null) { 310 return converter; 311 } 312 } 313 } 314 315 // only do these tests as fallback and only on the target type (eg not on its super) 316 if (!isSuper) { 317 if (fromType != null && !fromType.equals(Object.class)) { 318 319 // lets try classes derived from this toType 320 Set<Map.Entry<TypeMapping, TypeConverter>> entries = typeMappings.entrySet(); 321 for (Map.Entry<TypeMapping, TypeConverter> entry : entries) { 322 TypeMapping key = entry.getKey(); 323 Class aToType = key.getToType(); 324 if (toType.isAssignableFrom(aToType)) { 325 Class aFromType = key.getFromType(); 326 // skip Object based we do them last 327 if (!aFromType.equals(Object.class) && aFromType.isAssignableFrom(fromType)) { 328 return entry.getValue(); 329 } 330 } 331 } 332 333 // lets test for Object based converters as last resort 334 TypeConverter converter = getTypeConverter(toType, Object.class); 335 if (converter != null) { 336 return converter; 337 } 338 } 339 } 340 341 // none found 342 return null; 343 } 344 345 /** 346 * Checks if the registry is loaded and if not lazily load it 347 */ 348 protected synchronized void checkLoaded() { 349 if (!loaded) { 350 loaded = true; 351 try { 352 for (TypeConverterLoader typeConverterLoader : typeConverterLoaders) { 353 typeConverterLoader.load(this); 354 } 355 356 // lets try load any other fallback converters 357 try { 358 loadFallbackTypeConverters(); 359 } catch (NoFactoryAvailableException e) { 360 // ignore its fine to have none 361 } 362 } catch (Exception e) { 363 throw wrapRuntimeCamelException(e); 364 } 365 } 366 } 367 368 protected void loadFallbackTypeConverters() throws IOException, ClassNotFoundException { 369 List<TypeConverter> converters = factoryFinder.newInstances("FallbackTypeConverter", getInjector(), TypeConverter.class); 370 for (TypeConverter converter : converters) { 371 addFallbackTypeConverter(converter); 372 } 373 } 374 375 /** 376 * Represents a mapping from one type (which can be null) to another 377 */ 378 protected static class TypeMapping { 379 Class toType; 380 Class fromType; 381 382 public TypeMapping(Class toType, Class fromType) { 383 this.toType = toType; 384 this.fromType = fromType; 385 } 386 387 public Class getFromType() { 388 return fromType; 389 } 390 391 public Class getToType() { 392 return toType; 393 } 394 395 @Override 396 public boolean equals(Object object) { 397 if (object instanceof TypeMapping) { 398 TypeMapping that = (TypeMapping)object; 399 return ObjectHelper.equal(this.fromType, that.fromType) 400 && ObjectHelper.equal(this.toType, that.toType); 401 } 402 return false; 403 } 404 405 @Override 406 public int hashCode() { 407 int answer = toType.hashCode(); 408 if (fromType != null) { 409 answer *= 37 + fromType.hashCode(); 410 } 411 return answer; 412 } 413 414 @Override 415 public String toString() { 416 return "[" + fromType + "=>" + toType + "]"; 417 } 418 419 public boolean isApplicable(Class fromClass) { 420 return fromType.isAssignableFrom(fromClass); 421 } 422 } 423 }