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