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    }