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