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