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