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