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