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