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