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.Exchange;
029    import org.apache.camel.NoFactoryAvailableException;
030    import org.apache.camel.NoTypeConversionAvailableException;
031    import org.apache.camel.TypeConverter;
032    import org.apache.camel.spi.Injector;
033    import org.apache.camel.spi.PackageScanClassResolver;
034    import org.apache.camel.spi.TypeConverterAware;
035    import org.apache.camel.spi.TypeConverterRegistry;
036    import org.apache.camel.util.FactoryFinder;
037    import org.apache.camel.util.ObjectHelper;
038    import org.apache.commons.logging.Log;
039    import org.apache.commons.logging.LogFactory;
040    
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: 752464 $
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 Injector injector;
055        private final List<TypeConverterLoader> typeConverterLoaders = new ArrayList<TypeConverterLoader>();
056        private final List<TypeConverter> fallbackConverters = new ArrayList<TypeConverter>();
057        private boolean loaded;
058    
059        public DefaultTypeConverter(PackageScanClassResolver resolver, Injector injector) {
060            this.injector = injector;
061    
062            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 AsyncProcessorTypeConverter());
071        }
072    
073        public List<TypeConverterLoader> getTypeConverterLoaders() {
074            return typeConverterLoaders;
075        }
076    
077        /**
078         * Is there <b>NOT</b> a type converter registered being able to converter the
079         * given value to the type
080         * @param toType  the type to convert to
081         * @param fromType  the type to convert from
082         * @return <tt>true</tt> if there is <b>NOT</b> a converter, <tt>false</tt> if there is
083         */
084        @SuppressWarnings("unchecked")
085        public boolean hasNoConverterFor(Class toType, Class fromType) {
086            TypeMapping key = new TypeMapping(toType, fromType);
087            // we must only look in misses and not do the acutal convertions
088            // as for stream it can be impossible to re-read them and this
089            // method should not cause any overhead
090            return misses.containsKey(key);
091        }
092    
093        public <T> T convertTo(Class<T> type, Object value) {
094            return convertTo(type, null, value);
095        }
096    
097        public <T> T convertTo(Class<T> type, Exchange exchange, Object value) {
098            return doConvertTo(type, exchange, value);
099        }
100    
101        @SuppressWarnings("unchecked")
102        public <T> T doConvertTo(Class<T> type, Exchange exchange, Object value) {
103            if (LOG.isTraceEnabled()) {
104                LOG.trace("Converting " + (value == null ? "null" : value.getClass().getCanonicalName())
105                    + " -> " + type.getCanonicalName() + " with value: " + value);
106            }
107    
108            if (value == null) {
109                // lets avoid NullPointerException when converting to boolean for null values
110                if (boolean.class.isAssignableFrom(type)) {
111                    return (T) Boolean.FALSE;
112                }
113                return null;
114            }
115    
116            // same instance type
117            if (type.isInstance(value)) {
118                return type.cast(value);
119            }
120    
121            // make sure we have loaded the converters
122            checkLoaded();
123    
124            // try to find a suitable type converter
125            TypeConverter converter = getOrFindTypeConverter(type, value);
126            if (converter != null) {
127                T rc = converter.convertTo(type, exchange, value);
128                if (rc != null) {
129                    return rc;
130                }
131            }
132    
133            // fallback converters
134            for (TypeConverter fallback : fallbackConverters) {
135                T rc = fallback.convertTo(type, exchange, value);
136                if (rc != null) {
137                    return rc;
138                }
139            }
140    
141            // primitives
142            if (type.isPrimitive()) {
143                Class primitiveType = ObjectHelper.convertPrimitiveTypeToWrapperType(type);
144                if (primitiveType != type) {
145                    return (T) convertTo(primitiveType, exchange, value);
146                }
147            }
148    
149            synchronized (misses) {
150                TypeMapping key = new TypeMapping(type, value.getClass());
151                misses.put(key, key);
152            }
153    
154            // Could not find suitable conversion
155            throw new NoTypeConversionAvailableException(value, type);
156        }
157    
158        public void addTypeConverter(Class toType, Class fromType, TypeConverter typeConverter) {
159            if (LOG.isTraceEnabled()) {
160                LOG.trace("Adding type converter: " + typeConverter);
161            }
162            TypeMapping key = new TypeMapping(toType, fromType);
163            synchronized (typeMappings) {
164                TypeConverter converter = typeMappings.get(key);
165                if (converter != null) {
166                    LOG.warn("Overriding type converter from: " + converter + " to: " + typeConverter);
167                }
168                typeMappings.put(key, typeConverter);
169            }
170        }
171    
172        public void addFallbackTypeConverter(TypeConverter typeConverter) {
173            if (LOG.isTraceEnabled()) {
174                LOG.trace("Adding fallback type converter: " + typeConverter);
175            }
176    
177            // add in top of fallback as the toString() fallback will nearly always be able to convert
178            fallbackConverters.add(0, typeConverter);
179            if (typeConverter instanceof TypeConverterAware) {
180                TypeConverterAware typeConverterAware = (TypeConverterAware)typeConverter;
181                typeConverterAware.setTypeConverter(this);
182            }
183        }
184    
185        public TypeConverter getTypeConverter(Class toType, Class fromType) {
186            TypeMapping key = new TypeMapping(toType, fromType);
187            return typeMappings.get(key);
188        }
189    
190        public Injector getInjector() {
191            return injector;
192        }
193    
194        public void setInjector(Injector injector) {
195            this.injector = injector;
196        }
197    
198        public Set<Class> getFromClassMappings() {
199            // make sure we have loaded the converters
200            checkLoaded();
201    
202            Set<Class> answer = new HashSet<Class>();
203            synchronized (typeMappings) {
204                for (TypeMapping mapping : typeMappings.keySet()) {
205                    answer.add(mapping.getFromType());
206                }
207            }
208            return answer;
209        }
210    
211        public Map<Class, TypeConverter> getToClassMappings(Class fromClass) {
212            Map<Class, TypeConverter> answer = new HashMap<Class, TypeConverter>();
213            synchronized (typeMappings) {
214                for (Map.Entry<TypeMapping, TypeConverter> entry : typeMappings.entrySet()) {
215                    TypeMapping mapping = entry.getKey();
216                    if (mapping.isApplicable(fromClass)) {
217                        answer.put(mapping.getToType(), entry.getValue());
218                    }
219                }
220            }
221            return answer;
222        }
223    
224        public Map<TypeMapping, TypeConverter> getTypeMappings() {
225            // make sure we have loaded the converters
226            checkLoaded();
227    
228            return typeMappings;
229        }
230    
231        protected <T> TypeConverter getOrFindTypeConverter(Class toType, Object value) {
232            Class fromType = null;
233            if (value != null) {
234                fromType = value.getClass();
235            }
236            TypeMapping key = new TypeMapping(toType, fromType);
237            TypeConverter converter;
238            synchronized (typeMappings) {
239                converter = typeMappings.get(key);
240                if (converter == null) {
241                    converter = lookup(toType, fromType);
242                    if (converter != null) {
243                        typeMappings.put(key, converter);
244                    }
245                }
246            }
247            return converter;
248        }
249    
250        public TypeConverter lookup(Class toType, Class fromType) {
251            // make sure we have loaded the converters
252            checkLoaded();
253    
254            // lets try the super classes of the from type
255            if (fromType != null) {
256                Class fromSuperClass = fromType.getSuperclass();
257                if (fromSuperClass != null && !fromSuperClass.equals(Object.class)) {
258    
259                    TypeConverter converter = getTypeConverter(toType, fromSuperClass);
260                    if (converter == null) {
261                        converter = lookup(toType, fromSuperClass);
262                    }
263                    if (converter != null) {
264                        return converter;
265                    }
266                }
267                for (Class type : fromType.getInterfaces()) {
268                    TypeConverter converter = getTypeConverter(toType, type);
269                    if (converter != null) {
270                        return converter;
271                    }
272                }
273    
274                // lets test for arrays
275                if (fromType.isArray() && !fromType.getComponentType().isPrimitive()) {
276                    // TODO can we try walking the inheritance-tree for the element types?
277                    if (!fromType.equals(Object[].class)) {
278                        fromSuperClass = Object[].class;
279    
280                        TypeConverter converter = getTypeConverter(toType, fromSuperClass);
281                        if (converter == null) {
282                            converter = lookup(toType, fromSuperClass);
283                        }
284                        if (converter != null) {
285                            return converter;
286                        }
287                    }
288                }
289    
290                // lets test for Object based converters
291                if (!fromType.equals(Object.class)) {
292                    TypeConverter converter = getTypeConverter(toType, Object.class);
293                    if (converter != null) {
294                        return converter;
295                    }
296                }
297            }
298    
299            // lets try classes derived from this toType
300            if (fromType != null) {
301                Set<Map.Entry<TypeMapping, TypeConverter>> entries = typeMappings.entrySet();
302                for (Map.Entry<TypeMapping, TypeConverter> entry : entries) {
303                    TypeMapping key = entry.getKey();
304                    Class aToType = key.getToType();
305                    if (toType.isAssignableFrom(aToType)) {
306                        if (key.getFromType().isAssignableFrom(fromType)) {
307                            return entry.getValue();
308                        }
309                    }
310                }
311            }
312    
313            // TODO look at constructors of toType?
314            return null;
315        }
316    
317        /**
318         * Checks if the registry is loaded and if not lazily load it
319         */
320        protected synchronized void checkLoaded() {
321            if (!loaded) {
322                loaded = true;
323                try {
324                    for (TypeConverterLoader typeConverterLoader : typeConverterLoaders) {
325                        typeConverterLoader.load(this);
326                    }
327    
328                    // lets try load any other fallback converters
329                    try {
330                        loadFallbackTypeConverters();
331                    } catch (NoFactoryAvailableException e) {
332                        // ignore its fine to have none
333                    }
334                } catch (Exception e) {
335                    throw wrapRuntimeCamelException(e);
336                }
337            }
338        }
339    
340        protected void loadFallbackTypeConverters() throws IOException, ClassNotFoundException {
341            FactoryFinder finder = new FactoryFinder();
342            List<TypeConverter> converters = finder.newInstances("FallbackTypeConverter", getInjector(), TypeConverter.class);
343            for (TypeConverter converter : converters) {
344                addFallbackTypeConverter(converter);
345            }
346        }
347    
348        /**
349         * Represents a mapping from one type (which can be null) to another
350         */
351        protected static class TypeMapping {
352            Class toType;
353            Class fromType;
354    
355            public TypeMapping(Class toType, Class fromType) {
356                this.toType = toType;
357                this.fromType = fromType;
358            }
359    
360            public Class getFromType() {
361                return fromType;
362            }
363    
364            public Class getToType() {
365                return toType;
366            }
367    
368            @Override
369            public boolean equals(Object object) {
370                if (object instanceof TypeMapping) {
371                    TypeMapping that = (TypeMapping)object;
372                    return ObjectHelper.equal(this.fromType, that.fromType)
373                           && ObjectHelper.equal(this.toType, that.toType);
374                }
375                return false;
376            }
377    
378            @Override
379            public int hashCode() {
380                int answer = toType.hashCode();
381                if (fromType != null) {
382                    answer *= 37 + fromType.hashCode();
383                }
384                return answer;
385            }
386    
387            @Override
388            public String toString() {
389                return "[" + fromType + "=>" + toType + "]";
390            }
391    
392            public boolean isApplicable(Class fromClass) {
393                return fromType.isAssignableFrom(fromClass);
394            }
395        }
396    }