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.HashMap;
022 import java.util.HashSet;
023 import java.util.List;
024 import java.util.Map;
025 import java.util.Set;
026 import java.util.concurrent.ConcurrentHashMap;
027
028 import org.apache.camel.CamelExecutionException;
029 import org.apache.camel.Exchange;
030 import org.apache.camel.NoFactoryAvailableException;
031 import org.apache.camel.NoTypeConversionAvailableException;
032 import org.apache.camel.TypeConverter;
033 import org.apache.camel.spi.FactoryFinder;
034 import org.apache.camel.spi.Injector;
035 import org.apache.camel.spi.PackageScanClassResolver;
036 import org.apache.camel.spi.TypeConverterAware;
037 import org.apache.camel.spi.TypeConverterRegistry;
038 import org.apache.camel.util.ObjectHelper;
039 import org.apache.commons.logging.Log;
040 import org.apache.commons.logging.LogFactory;
041 import static org.apache.camel.util.ObjectHelper.wrapRuntimeCamelException;
042
043
044 /**
045 * Default implementation of a type converter registry used for
046 * <a href="http://camel.apache.org/type-converter.html">type converters</a> in Camel.
047 *
048 * @version $Revision: 793935 $
049 */
050 public class DefaultTypeConverter implements TypeConverter, TypeConverterRegistry {
051 private static final transient Log LOG = LogFactory.getLog(DefaultTypeConverter.class);
052 private final Map<TypeMapping, TypeConverter> typeMappings = new ConcurrentHashMap<TypeMapping, TypeConverter>();
053 private final Map<TypeMapping, TypeMapping> misses = new ConcurrentHashMap<TypeMapping, TypeMapping>();
054 private final List<TypeConverterLoader> typeConverterLoaders = new ArrayList<TypeConverterLoader>();
055 private final List<TypeConverter> fallbackConverters = new ArrayList<TypeConverter>();
056 private Injector injector;
057 private final FactoryFinder factoryFinder;
058 private boolean loaded;
059
060 public DefaultTypeConverter(PackageScanClassResolver resolver, Injector injector, FactoryFinder factoryFinder) {
061 this.injector = injector;
062 this.factoryFinder = factoryFinder;
063
064 typeConverterLoaders.add(new AnnotationTypeConverterLoader(resolver));
065
066 // add to string first as it will then be last in the last as to string can nearly
067 // always convert something to a string so we want it only as the last resort
068 addFallbackTypeConverter(new ToStringTypeConverter());
069 addFallbackTypeConverter(new EnumTypeConverter());
070 addFallbackTypeConverter(new ArrayTypeConverter());
071 addFallbackTypeConverter(new PropertyEditorTypeConverter());
072 addFallbackTypeConverter(new FutureTypeConverter(this));
073 }
074
075 public List<TypeConverterLoader> getTypeConverterLoaders() {
076 return typeConverterLoaders;
077 }
078
079 public <T> T convertTo(Class<T> type, Object value) {
080 return convertTo(type, null, value);
081 }
082
083 public <T> T convertTo(Class<T> type, Exchange exchange, Object value) {
084 Object answer;
085 try {
086 answer = doConvertTo(type, exchange, value);
087 } catch (CamelExecutionException e) {
088 // rethrow exception exception as its not due to failed convertion
089 throw e;
090 } catch (Exception e) {
091 // we cannot convert so return null
092 if (LOG.isDebugEnabled()) {
093 LOG.debug(NoTypeConversionAvailableException.createMessage(value, type)
094 + " Caused by: " + e.getMessage() + ". Will ignore this and continue.");
095 }
096 return null;
097 }
098 if (answer == Void.TYPE) {
099 // Could not find suitable conversion
100 return null;
101 } else {
102 return (T) answer;
103 }
104 }
105
106 public <T> T mandatoryConvertTo(Class<T> type, Object value) throws NoTypeConversionAvailableException {
107 return mandatoryConvertTo(type, null, value);
108 }
109
110 public <T> T mandatoryConvertTo(Class<T> type, Exchange exchange, Object value) throws NoTypeConversionAvailableException {
111 Object answer;
112 try {
113 answer = doConvertTo(type, exchange, value);
114 } catch (Exception e) {
115 throw new NoTypeConversionAvailableException(value, type, e);
116 }
117 if (answer == Void.TYPE) {
118 // Could not find suitable conversion
119 throw new NoTypeConversionAvailableException(value, type);
120 } else {
121 return (T) answer;
122 }
123 }
124
125 @SuppressWarnings("unchecked")
126 public Object doConvertTo(final Class type, final Exchange exchange, final Object value) {
127 if (LOG.isTraceEnabled()) {
128 LOG.trace("Converting " + (value == null ? "null" : value.getClass().getCanonicalName())
129 + " -> " + type.getCanonicalName() + " with value: " + value);
130 }
131
132 if (value == null) {
133 // lets avoid NullPointerException when converting to boolean for null values
134 if (boolean.class.isAssignableFrom(type)) {
135 return Boolean.FALSE;
136 }
137 return null;
138 }
139
140 // same instance type
141 if (type.isInstance(value)) {
142 return type.cast(value);
143 }
144
145 // check if we have tried it before and if its a miss
146 TypeMapping key = new TypeMapping(type, value.getClass());
147 if (misses.containsKey(key)) {
148 // we have tried before but we cannot convert this one
149 return Void.TYPE;
150 }
151
152 // make sure we have loaded the converters
153 checkLoaded();
154
155 // try to find a suitable type converter
156 TypeConverter converter = getOrFindTypeConverter(type, value);
157 if (converter != null) {
158 Object rc = converter.convertTo(type, exchange, value);
159 if (rc != null) {
160 return rc;
161 }
162 }
163
164 // fallback converters
165 for (TypeConverter fallback : fallbackConverters) {
166 Object rc = fallback.convertTo(type, exchange, value);
167 if (rc != null) {
168 // add it as a known type converter since we found a fallback that could do it
169 if (LOG.isDebugEnabled()) {
170 LOG.debug("Adding fallback type converter as a known type converter to convert from: "
171 + type.getCanonicalName() + " to: " + value.getClass().getCanonicalName());
172 }
173 addTypeConverter(type, value.getClass(), fallback);
174 return rc;
175 }
176 }
177
178 // primitives
179 if (type.isPrimitive()) {
180 Class primitiveType = ObjectHelper.convertPrimitiveTypeToWrapperType(type);
181 if (primitiveType != type) {
182 return convertTo(primitiveType, exchange, value);
183 }
184 }
185
186 // Could not find suitable conversion, so remember it
187 synchronized (misses) {
188 misses.put(key, key);
189 }
190
191 // Could not find suitable conversion, so return Void to indicate not found
192 return Void.TYPE;
193 }
194
195 public void addTypeConverter(Class toType, Class fromType, TypeConverter typeConverter) {
196 if (LOG.isTraceEnabled()) {
197 LOG.trace("Adding type converter: " + typeConverter);
198 }
199 TypeMapping key = new TypeMapping(toType, fromType);
200 synchronized (typeMappings) {
201 TypeConverter converter = typeMappings.get(key);
202 if (converter != null) {
203 LOG.warn("Overriding type converter from: " + converter + " to: " + typeConverter);
204 }
205 typeMappings.put(key, typeConverter);
206 }
207 }
208
209 public void addFallbackTypeConverter(TypeConverter typeConverter) {
210 if (LOG.isTraceEnabled()) {
211 LOG.trace("Adding fallback type converter: " + typeConverter);
212 }
213
214 // add in top of fallback as the toString() fallback will nearly always be able to convert
215 fallbackConverters.add(0, typeConverter);
216 if (typeConverter instanceof TypeConverterAware) {
217 TypeConverterAware typeConverterAware = (TypeConverterAware)typeConverter;
218 typeConverterAware.setTypeConverter(this);
219 }
220 }
221
222 public TypeConverter getTypeConverter(Class toType, Class fromType) {
223 TypeMapping key = new TypeMapping(toType, fromType);
224 return typeMappings.get(key);
225 }
226
227 public Injector getInjector() {
228 return injector;
229 }
230
231 public void setInjector(Injector injector) {
232 this.injector = injector;
233 }
234
235 public Set<Class> getFromClassMappings() {
236 // make sure we have loaded the converters
237 checkLoaded();
238
239 Set<Class> answer = new HashSet<Class>();
240 synchronized (typeMappings) {
241 for (TypeMapping mapping : typeMappings.keySet()) {
242 answer.add(mapping.getFromType());
243 }
244 }
245 return answer;
246 }
247
248 public Map<Class, TypeConverter> getToClassMappings(Class fromClass) {
249 Map<Class, TypeConverter> answer = new HashMap<Class, TypeConverter>();
250 synchronized (typeMappings) {
251 for (Map.Entry<TypeMapping, TypeConverter> entry : typeMappings.entrySet()) {
252 TypeMapping mapping = entry.getKey();
253 if (mapping.isApplicable(fromClass)) {
254 answer.put(mapping.getToType(), entry.getValue());
255 }
256 }
257 }
258 return answer;
259 }
260
261 public Map<TypeMapping, TypeConverter> getTypeMappings() {
262 // make sure we have loaded the converters
263 checkLoaded();
264
265 return typeMappings;
266 }
267
268 protected <T> TypeConverter getOrFindTypeConverter(Class toType, Object value) {
269 Class fromType = null;
270 if (value != null) {
271 fromType = value.getClass();
272 }
273 TypeMapping key = new TypeMapping(toType, fromType);
274 TypeConverter converter;
275 synchronized (typeMappings) {
276 converter = typeMappings.get(key);
277 if (converter == null) {
278 converter = lookup(toType, fromType);
279 if (converter != null) {
280 typeMappings.put(key, converter);
281 }
282 }
283 }
284 return converter;
285 }
286
287 public TypeConverter lookup(Class toType, Class fromType) {
288 // make sure we have loaded the converters
289 checkLoaded();
290
291 return doLookup(toType, fromType, false);
292 }
293
294 private TypeConverter doLookup(Class toType, Class fromType, boolean isSuper) {
295
296 if (fromType != null) {
297 // lets try if there is a direct match
298 TypeConverter converter = getTypeConverter(toType, fromType);
299 if (converter != null) {
300 return converter;
301 }
302
303 // try the interfaces
304 for (Class type : fromType.getInterfaces()) {
305 converter = getTypeConverter(toType, type);
306 if (converter != null) {
307 return converter;
308 }
309 }
310
311 // try super then
312 Class fromSuperClass = fromType.getSuperclass();
313 if (fromSuperClass != null && !fromSuperClass.equals(Object.class)) {
314 converter = doLookup(toType, fromSuperClass, true);
315 if (converter != null) {
316 return converter;
317 }
318 }
319 }
320
321 // only do these tests as fallback and only on the target type (eg not on its super)
322 if (!isSuper) {
323 if (fromType != null && !fromType.equals(Object.class)) {
324
325 // lets try classes derived from this toType
326 Set<Map.Entry<TypeMapping, TypeConverter>> entries = typeMappings.entrySet();
327 for (Map.Entry<TypeMapping, TypeConverter> entry : entries) {
328 TypeMapping key = entry.getKey();
329 Class aToType = key.getToType();
330 if (toType.isAssignableFrom(aToType)) {
331 Class aFromType = key.getFromType();
332 // skip Object based we do them last
333 if (!aFromType.equals(Object.class) && aFromType.isAssignableFrom(fromType)) {
334 return entry.getValue();
335 }
336 }
337 }
338
339 // lets test for Object based converters as last resort
340 TypeConverter converter = getTypeConverter(toType, Object.class);
341 if (converter != null) {
342 return converter;
343 }
344 }
345 }
346
347 // none found
348 return null;
349 }
350
351 /**
352 * Checks if the registry is loaded and if not lazily load it
353 */
354 protected synchronized void checkLoaded() {
355 if (!loaded) {
356 loaded = true;
357 try {
358 for (TypeConverterLoader typeConverterLoader : typeConverterLoaders) {
359 typeConverterLoader.load(this);
360 }
361
362 // lets try load any other fallback converters
363 try {
364 loadFallbackTypeConverters();
365 } catch (NoFactoryAvailableException e) {
366 // ignore its fine to have none
367 }
368 } catch (Exception e) {
369 throw wrapRuntimeCamelException(e);
370 }
371 }
372 }
373
374 protected void loadFallbackTypeConverters() throws IOException, ClassNotFoundException {
375 List<TypeConverter> converters = factoryFinder.newInstances("FallbackTypeConverter", getInjector(), TypeConverter.class);
376 for (TypeConverter converter : converters) {
377 addFallbackTypeConverter(converter);
378 }
379 }
380
381 /**
382 * Represents a mapping from one type (which can be null) to another
383 */
384 protected static class TypeMapping {
385 Class toType;
386 Class fromType;
387
388 public TypeMapping(Class toType, Class fromType) {
389 this.toType = toType;
390 this.fromType = fromType;
391 }
392
393 public Class getFromType() {
394 return fromType;
395 }
396
397 public Class getToType() {
398 return toType;
399 }
400
401 @Override
402 public boolean equals(Object object) {
403 if (object instanceof TypeMapping) {
404 TypeMapping that = (TypeMapping)object;
405 return ObjectHelper.equal(this.fromType, that.fromType)
406 && ObjectHelper.equal(this.toType, that.toType);
407 }
408 return false;
409 }
410
411 @Override
412 public int hashCode() {
413 int answer = toType.hashCode();
414 if (fromType != null) {
415 answer *= 37 + fromType.hashCode();
416 }
417 return answer;
418 }
419
420 @Override
421 public String toString() {
422 return "[" + fromType + "=>" + toType + "]";
423 }
424
425 public boolean isApplicable(Class fromClass) {
426 return fromType.isAssignableFrom(fromClass);
427 }
428 }
429 }