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