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