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: 772598 $
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                    return rc;
169                }
170            }
171    
172            // primitives
173            if (type.isPrimitive()) {
174                Class primitiveType = ObjectHelper.convertPrimitiveTypeToWrapperType(type);
175                if (primitiveType != type) {
176                    return convertTo(primitiveType, exchange, value);
177                }
178            }
179    
180            // Could not find suitable conversion, so remember it
181            synchronized (misses) {
182                misses.put(key, key);
183            }
184    
185            // Could not find suitable conversion, so return Void to indicate not found
186            return Void.TYPE;
187        }
188    
189        public void addTypeConverter(Class toType, Class fromType, TypeConverter typeConverter) {
190            if (LOG.isTraceEnabled()) {
191                LOG.trace("Adding type converter: " + typeConverter);
192            }
193            TypeMapping key = new TypeMapping(toType, fromType);
194            synchronized (typeMappings) {
195                TypeConverter converter = typeMappings.get(key);
196                if (converter != null) {
197                    LOG.warn("Overriding type converter from: " + converter + " to: " + typeConverter);
198                }
199                typeMappings.put(key, typeConverter);
200            }
201        }
202    
203        public void addFallbackTypeConverter(TypeConverter typeConverter) {
204            if (LOG.isTraceEnabled()) {
205                LOG.trace("Adding fallback type converter: " + typeConverter);
206            }
207    
208            // add in top of fallback as the toString() fallback will nearly always be able to convert
209            fallbackConverters.add(0, typeConverter);
210            if (typeConverter instanceof TypeConverterAware) {
211                TypeConverterAware typeConverterAware = (TypeConverterAware)typeConverter;
212                typeConverterAware.setTypeConverter(this);
213            }
214        }
215    
216        public TypeConverter getTypeConverter(Class toType, Class fromType) {
217            TypeMapping key = new TypeMapping(toType, fromType);
218            return typeMappings.get(key);
219        }
220    
221        public Injector getInjector() {
222            return injector;
223        }
224    
225        public void setInjector(Injector injector) {
226            this.injector = injector;
227        }
228    
229        public Set<Class> getFromClassMappings() {
230            // make sure we have loaded the converters
231            checkLoaded();
232    
233            Set<Class> answer = new HashSet<Class>();
234            synchronized (typeMappings) {
235                for (TypeMapping mapping : typeMappings.keySet()) {
236                    answer.add(mapping.getFromType());
237                }
238            }
239            return answer;
240        }
241    
242        public Map<Class, TypeConverter> getToClassMappings(Class fromClass) {
243            Map<Class, TypeConverter> answer = new HashMap<Class, TypeConverter>();
244            synchronized (typeMappings) {
245                for (Map.Entry<TypeMapping, TypeConverter> entry : typeMappings.entrySet()) {
246                    TypeMapping mapping = entry.getKey();
247                    if (mapping.isApplicable(fromClass)) {
248                        answer.put(mapping.getToType(), entry.getValue());
249                    }
250                }
251            }
252            return answer;
253        }
254    
255        public Map<TypeMapping, TypeConverter> getTypeMappings() {
256            // make sure we have loaded the converters
257            checkLoaded();
258    
259            return typeMappings;
260        }
261    
262        protected <T> TypeConverter getOrFindTypeConverter(Class toType, Object value) {
263            Class fromType = null;
264            if (value != null) {
265                fromType = value.getClass();
266            }
267            TypeMapping key = new TypeMapping(toType, fromType);
268            TypeConverter converter;
269            synchronized (typeMappings) {
270                converter = typeMappings.get(key);
271                if (converter == null) {
272                    converter = lookup(toType, fromType);
273                    if (converter != null) {
274                        typeMappings.put(key, converter);
275                    }
276                }
277            }
278            return converter;
279        }
280    
281        public TypeConverter lookup(Class toType, Class fromType) {
282            // make sure we have loaded the converters
283            checkLoaded();
284    
285            return doLookup(toType, fromType, false);
286        }
287    
288        private TypeConverter doLookup(Class toType, Class fromType, boolean isSuper) {
289    
290            if (fromType != null) {
291                // lets try if there is a direct match
292                TypeConverter converter = getTypeConverter(toType, fromType);
293                if (converter != null) {
294                    return converter;
295                }
296    
297                // try the interfaces
298                for (Class type : fromType.getInterfaces()) {
299                    converter = getTypeConverter(toType, type);
300                    if (converter != null) {
301                        return converter;
302                    }
303                }
304    
305                // try super then
306                Class fromSuperClass = fromType.getSuperclass();
307                if (fromSuperClass != null && !fromSuperClass.equals(Object.class)) {
308                    converter = doLookup(toType, fromSuperClass, true);
309                    if (converter != null) {
310                        return converter;
311                    }
312                }
313            }
314    
315            // only do these tests as fallback and only on the target type (eg not on its super)
316            if (!isSuper) {
317                if (fromType != null && !fromType.equals(Object.class)) {
318    
319                    // lets try classes derived from this toType
320                    Set<Map.Entry<TypeMapping, TypeConverter>> entries = typeMappings.entrySet();
321                    for (Map.Entry<TypeMapping, TypeConverter> entry : entries) {
322                        TypeMapping key = entry.getKey();
323                        Class aToType = key.getToType();
324                        if (toType.isAssignableFrom(aToType)) {
325                            Class aFromType = key.getFromType();
326                            // skip Object based we do them last
327                            if (!aFromType.equals(Object.class) && aFromType.isAssignableFrom(fromType)) {
328                                return entry.getValue();
329                            }
330                        }
331                    }
332    
333                    // lets test for Object based converters as last resort
334                    TypeConverter converter = getTypeConverter(toType, Object.class);
335                    if (converter != null) {
336                        return converter;
337                    }
338                }
339            }
340    
341            // none found
342            return null;
343        }
344    
345        /**
346         * Checks if the registry is loaded and if not lazily load it
347         */
348        protected synchronized void checkLoaded() {
349            if (!loaded) {
350                loaded = true;
351                try {
352                    for (TypeConverterLoader typeConverterLoader : typeConverterLoaders) {
353                        typeConverterLoader.load(this);
354                    }
355    
356                    // lets try load any other fallback converters
357                    try {
358                        loadFallbackTypeConverters();
359                    } catch (NoFactoryAvailableException e) {
360                        // ignore its fine to have none
361                    }
362                } catch (Exception e) {
363                    throw wrapRuntimeCamelException(e);
364                }
365            }
366        }
367    
368        protected void loadFallbackTypeConverters() throws IOException, ClassNotFoundException {
369            List<TypeConverter> converters = factoryFinder.newInstances("FallbackTypeConverter", getInjector(), TypeConverter.class);
370            for (TypeConverter converter : converters) {
371                addFallbackTypeConverter(converter);
372            }
373        }
374    
375        /**
376         * Represents a mapping from one type (which can be null) to another
377         */
378        protected static class TypeMapping {
379            Class toType;
380            Class fromType;
381    
382            public TypeMapping(Class toType, Class fromType) {
383                this.toType = toType;
384                this.fromType = fromType;
385            }
386    
387            public Class getFromType() {
388                return fromType;
389            }
390    
391            public Class getToType() {
392                return toType;
393            }
394    
395            @Override
396            public boolean equals(Object object) {
397                if (object instanceof TypeMapping) {
398                    TypeMapping that = (TypeMapping)object;
399                    return ObjectHelper.equal(this.fromType, that.fromType)
400                           && ObjectHelper.equal(this.toType, that.toType);
401                }
402                return false;
403            }
404    
405            @Override
406            public int hashCode() {
407                int answer = toType.hashCode();
408                if (fromType != null) {
409                    answer *= 37 + fromType.hashCode();
410                }
411                return answer;
412            }
413    
414            @Override
415            public String toString() {
416                return "[" + fromType + "=>" + toType + "]";
417            }
418    
419            public boolean isApplicable(Class fromClass) {
420                return fromType.isAssignableFrom(fromClass);
421            }
422        }
423    }