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    }