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