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