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: 14157 $ 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 // add it as a known type converter since we found a fallback that could do it 130 if (LOG.isDebugEnabled()) { 131 LOG.debug("Adding fallback type converter as a known type converter to convert from: " 132 + type.getCanonicalName() + " to: " + value.getClass().getCanonicalName()); 133 } 134 addTypeConverter(type, value.getClass(), fallback); 135 return rc; 136 } 137 } 138 139 // primitives 140 if (type.isPrimitive()) { 141 Class primitiveType = ObjectHelper.convertPrimitiveTypeToWrapperType(type); 142 if (primitiveType != type) { 143 return (T) convertTo(primitiveType, exchange, value); 144 } 145 } 146 147 synchronized (misses) { 148 TypeMapping key = new TypeMapping(type, value.getClass()); 149 misses.put(key, key); 150 } 151 152 // Could not find suitable conversion 153 throw new NoTypeConversionAvailableException(value, type); 154 } 155 156 public void addTypeConverter(Class toType, Class fromType, TypeConverter typeConverter) { 157 TypeMapping key = new TypeMapping(toType, fromType); 158 synchronized (typeMappings) { 159 TypeConverter converter = typeMappings.get(key); 160 if (converter != null) { 161 LOG.warn("Overriding type converter from: " + converter + " to: " + typeConverter); 162 } 163 typeMappings.put(key, typeConverter); 164 } 165 } 166 167 public void addFallbackConverter(TypeConverter converter) { 168 fallbackConverters.add(converter); 169 if (converter instanceof TypeConverterAware) { 170 TypeConverterAware typeConverterAware = (TypeConverterAware)converter; 171 typeConverterAware.setTypeConverter(this); 172 } 173 } 174 175 public TypeConverter getTypeConverter(Class toType, Class fromType) { 176 TypeMapping key = new TypeMapping(toType, fromType); 177 return typeMappings.get(key); 178 } 179 180 public Injector getInjector() { 181 return injector; 182 } 183 184 public void setInjector(Injector injector) { 185 this.injector = injector; 186 } 187 188 protected <T> TypeConverter getOrFindTypeConverter(Class toType, Object value) { 189 Class fromType = null; 190 if (value != null) { 191 fromType = value.getClass(); 192 } 193 TypeMapping key = new TypeMapping(toType, fromType); 194 TypeConverter converter; 195 synchronized (typeMappings) { 196 converter = typeMappings.get(key); 197 if (converter == null) { 198 converter = findTypeConverter(toType, fromType, value); 199 if (converter != null) { 200 typeMappings.put(key, converter); 201 } 202 } 203 } 204 return converter; 205 } 206 207 /** 208 * Tries to auto-discover any available type converters 209 */ 210 protected TypeConverter findTypeConverter(Class toType, Class fromType, Object value) { 211 // lets try the super classes of the from type 212 if (fromType != null) { 213 Class fromSuperClass = fromType.getSuperclass(); 214 if (fromSuperClass != null && !fromSuperClass.equals(Object.class)) { 215 216 TypeConverter converter = getTypeConverter(toType, fromSuperClass); 217 if (converter == null) { 218 converter = findTypeConverter(toType, fromSuperClass, value); 219 } 220 if (converter != null) { 221 return converter; 222 } 223 } 224 for (Class type : fromType.getInterfaces()) { 225 TypeConverter converter = getTypeConverter(toType, type); 226 if (converter != null) { 227 return converter; 228 } 229 } 230 231 // lets test for arrays 232 if (fromType.isArray() && !fromType.getComponentType().isPrimitive()) { 233 // TODO can we try walking the inheritance-tree for the element types? 234 if (!fromType.equals(Object[].class)) { 235 fromSuperClass = Object[].class; 236 237 TypeConverter converter = getTypeConverter(toType, fromSuperClass); 238 if (converter == null) { 239 converter = findTypeConverter(toType, fromSuperClass, value); 240 } 241 if (converter != null) { 242 return converter; 243 } 244 } 245 } 246 247 // lets test for Object based converters 248 if (!fromType.equals(Object.class)) { 249 TypeConverter converter = getTypeConverter(toType, Object.class); 250 if (converter != null) { 251 return converter; 252 } 253 } 254 } 255 256 // lets try classes derived from this toType 257 if (fromType != null) { 258 Set<Map.Entry<TypeMapping, TypeConverter>> entries = typeMappings.entrySet(); 259 for (Map.Entry<TypeMapping, TypeConverter> entry : entries) { 260 TypeMapping key = entry.getKey(); 261 Class aToType = key.getToType(); 262 if (toType.isAssignableFrom(aToType)) { 263 if (key.getFromType().isAssignableFrom(fromType)) { 264 return entry.getValue(); 265 } 266 } 267 } 268 } 269 270 // TODO look at constructors of toType? 271 return null; 272 } 273 274 /** 275 * Checks if the registry is loaded and if not lazily load it 276 */ 277 protected synchronized void checkLoaded() { 278 if (!loaded) { 279 loaded = true; 280 try { 281 for (TypeConverterLoader typeConverterLoader : typeConverterLoaders) { 282 typeConverterLoader.load(this); 283 } 284 285 // lets try load any other fallback converters 286 try { 287 loadFallbackTypeConverters(); 288 } catch (NoFactoryAvailableException e) { 289 // ignore its fine to have none 290 } 291 } catch (Exception e) { 292 throw wrapRuntimeCamelException(e); 293 } 294 } 295 } 296 297 protected void loadFallbackTypeConverters() throws IOException, ClassNotFoundException { 298 FactoryFinder finder = new FactoryFinder(); 299 List<TypeConverter> converters = finder.newInstances("FallbackTypeConverter", getInjector(), 300 TypeConverter.class); 301 for (TypeConverter converter : converters) { 302 addFallbackConverter(converter); 303 } 304 } 305 306 /** 307 * Represents a mapping from one type (which can be null) to another 308 */ 309 protected static class TypeMapping { 310 Class toType; 311 Class fromType; 312 313 public TypeMapping(Class toType, Class fromType) { 314 this.toType = toType; 315 this.fromType = fromType; 316 } 317 318 public Class getFromType() { 319 return fromType; 320 } 321 322 public Class getToType() { 323 return toType; 324 } 325 326 @Override 327 public boolean equals(Object object) { 328 if (object instanceof TypeMapping) { 329 TypeMapping that = (TypeMapping)object; 330 return ObjectHelper.equal(this.fromType, that.fromType) 331 && ObjectHelper.equal(this.toType, that.toType); 332 } 333 return false; 334 } 335 336 @Override 337 public int hashCode() { 338 int answer = toType.hashCode(); 339 if (fromType != null) { 340 answer *= 37 + fromType.hashCode(); 341 } 342 return answer; 343 } 344 345 @Override 346 public String toString() { 347 return "[" + fromType + "=>" + toType + "]"; 348 } 349 } 350 }