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