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.HashSet;
023    import java.util.List;
024    import java.util.Map;
025    import java.util.Set;
026    import java.util.concurrent.ConcurrentHashMap;
027    import java.util.concurrent.ConcurrentMap;
028    import java.util.concurrent.CopyOnWriteArrayList;
029    import java.util.concurrent.ExecutionException;
030    import java.util.concurrent.atomic.AtomicLong;
031    
032    import org.apache.camel.CamelExecutionException;
033    import org.apache.camel.Exchange;
034    import org.apache.camel.NoFactoryAvailableException;
035    import org.apache.camel.NoTypeConversionAvailableException;
036    import org.apache.camel.TypeConversionException;
037    import org.apache.camel.TypeConverter;
038    import org.apache.camel.spi.FactoryFinder;
039    import org.apache.camel.spi.Injector;
040    import org.apache.camel.spi.PackageScanClassResolver;
041    import org.apache.camel.spi.TypeConverterAware;
042    import org.apache.camel.spi.TypeConverterLoader;
043    import org.apache.camel.spi.TypeConverterRegistry;
044    import org.apache.camel.support.ServiceSupport;
045    import org.apache.camel.util.LRUSoftCache;
046    import org.apache.camel.util.ObjectHelper;
047    import org.slf4j.Logger;
048    import org.slf4j.LoggerFactory;
049    
050    /**
051     * Base implementation of a type converter registry used for
052     * <a href="http://camel.apache.org/type-converter.html">type converters</a> in Camel.
053     *
054     * @version 
055     */
056    public abstract class BaseTypeConverterRegistry extends ServiceSupport implements TypeConverter, TypeConverterRegistry {
057        protected final transient Logger log = LoggerFactory.getLogger(getClass());
058        protected final ConcurrentMap<TypeMapping, TypeConverter> typeMappings = new ConcurrentHashMap<TypeMapping, TypeConverter>();
059        // for misses use a soft reference cache map, as the classes may be un-deployed at runtime
060        protected final LRUSoftCache<TypeMapping, TypeMapping> misses = new LRUSoftCache<TypeMapping, TypeMapping>(1000);
061        protected final List<TypeConverterLoader> typeConverterLoaders = new ArrayList<TypeConverterLoader>();
062        protected final List<FallbackTypeConverter> fallbackConverters = new CopyOnWriteArrayList<FallbackTypeConverter>();
063        protected final PackageScanClassResolver resolver;
064        protected Injector injector;
065        protected final FactoryFinder factoryFinder;
066        protected final Statistics statistics = new UtilizationStatistics();
067        protected final AtomicLong attemptCounter = new AtomicLong();
068        protected final AtomicLong missCounter = new AtomicLong();
069        protected final AtomicLong hitCounter = new AtomicLong();
070        protected final AtomicLong failedCounter = new AtomicLong();
071    
072        public BaseTypeConverterRegistry(PackageScanClassResolver resolver, Injector injector, FactoryFinder factoryFinder) {
073            this.resolver = resolver;
074            this.injector = injector;
075            this.factoryFinder = factoryFinder;
076            this.typeConverterLoaders.add(new AnnotationTypeConverterLoader(resolver));
077    
078            // add to string first as it will then be last in the last as to string can nearly
079            // always convert something to a string so we want it only as the last resort
080            // ToStringTypeConverter should NOT allow to be promoted
081            addFallbackTypeConverter(new ToStringTypeConverter(), false);
082            // enum is okay to be promoted
083            addFallbackTypeConverter(new EnumTypeConverter(), true);
084            // arrays is okay to be promoted
085            addFallbackTypeConverter(new ArrayTypeConverter(), true);
086            // and future should also not allowed to be promoted
087            addFallbackTypeConverter(new FutureTypeConverter(this), false);
088            // add sync processor to async processor converter is to be promoted
089            addFallbackTypeConverter(new AsyncProcessorTypeConverter(), true);
090        }
091    
092        public List<TypeConverterLoader> getTypeConverterLoaders() {
093            return typeConverterLoaders;
094        }
095    
096        @Override
097        public <T> T convertTo(Class<T> type, Object value) {
098            return convertTo(type, null, value);
099        }
100    
101        @SuppressWarnings("unchecked")
102        @Override
103        public <T> T convertTo(Class<T> type, Exchange exchange, Object value) {
104            if (!isRunAllowed()) {
105                throw new IllegalStateException(this + " is not started");
106            }
107    
108            Object answer;
109            try {
110                attemptCounter.incrementAndGet();
111                answer = doConvertTo(type, exchange, value, false);
112            } catch (Exception e) {
113                failedCounter.incrementAndGet();
114                // if its a ExecutionException then we have rethrow it as its not due to failed conversion
115                // this is special for FutureTypeConverter
116                boolean execution = ObjectHelper.getException(ExecutionException.class, e) != null
117                        || ObjectHelper.getException(CamelExecutionException.class, e) != null;
118                if (execution) {
119                    throw ObjectHelper.wrapCamelExecutionException(exchange, e);
120                }
121    
122                // error occurred during type conversion
123                if (e instanceof TypeConversionException) {
124                    throw (TypeConversionException) e;
125                } else {
126                    throw new TypeConversionException(value, type, e);
127                }
128            }
129            if (answer == Void.TYPE) {
130                // Could not find suitable conversion
131                missCounter.incrementAndGet();
132                // Could not find suitable conversion
133                return null;
134            } else {
135                hitCounter.incrementAndGet();
136                return (T) answer;
137            }
138        }
139    
140        @Override
141        public <T> T mandatoryConvertTo(Class<T> type, Object value) throws NoTypeConversionAvailableException {
142            return mandatoryConvertTo(type, null, value);
143        }
144    
145        @SuppressWarnings("unchecked")
146        @Override
147        public <T> T mandatoryConvertTo(Class<T> type, Exchange exchange, Object value) throws NoTypeConversionAvailableException {
148            if (!isRunAllowed()) {
149                throw new IllegalStateException(this + " is not started");
150            }
151    
152            Object answer;
153            try {
154                attemptCounter.incrementAndGet();
155                answer = doConvertTo(type, exchange, value, false);
156            } catch (Exception e) {
157                failedCounter.incrementAndGet();
158                // error occurred during type conversion
159                if (e instanceof TypeConversionException) {
160                    throw (TypeConversionException) e;
161                } else {
162                    throw new TypeConversionException(value, type, e);
163                }
164            }
165            if (answer == Void.TYPE || value == null) {
166                // Could not find suitable conversion
167                missCounter.incrementAndGet();
168                // Could not find suitable conversion
169                throw new NoTypeConversionAvailableException(value, type);
170            } else {
171                hitCounter.incrementAndGet();
172                return (T) answer;
173            }
174        }
175    
176        @Override
177        public <T> T tryConvertTo(Class<T> type, Object value) {
178            return tryConvertTo(type, null, value);
179        }
180    
181        @SuppressWarnings("unchecked")
182        @Override
183        public <T> T tryConvertTo(Class<T> type, Exchange exchange, Object value) {
184            if (!isRunAllowed()) {
185                return null;
186            }
187    
188            Object answer;
189            try {
190                attemptCounter.incrementAndGet();
191                answer = doConvertTo(type, exchange, value, true);
192            } catch (Exception e) {
193                failedCounter.incrementAndGet();
194                return null;
195            }
196            if (answer == Void.TYPE) {
197                missCounter.incrementAndGet();
198                // Could not find suitable conversion
199                return null;
200            } else {
201                hitCounter.incrementAndGet();
202                return (T) answer;
203            }
204        }
205    
206        protected Object doConvertTo(final Class<?> type, final Exchange exchange, final Object value, final boolean tryConvert) {
207            if (log.isTraceEnabled()) {
208                log.trace("Converting {} -> {} with value: {}",
209                        new Object[]{value == null ? "null" : value.getClass().getCanonicalName(), 
210                            type.getCanonicalName(), value});
211            }
212    
213            if (value == null) {
214                // lets avoid NullPointerException when converting to boolean for null values
215                if (boolean.class.isAssignableFrom(type)) {
216                    return Boolean.FALSE;
217                }
218                return null;
219            }
220    
221            // same instance type
222            if (type.isInstance(value)) {
223                return type.cast(value);
224            }
225    
226            // check if we have tried it before and if its a miss
227            TypeMapping key = new TypeMapping(type, value.getClass());
228            if (misses.containsKey(key)) {
229                // we have tried before but we cannot convert this one
230                return Void.TYPE;
231            }
232            
233            // special for NaN numbers, which we can only convert for floating numbers
234            if (ObjectHelper.isNaN(value)) {
235                if (Float.class.isAssignableFrom(type)) {
236                    return Float.NaN;
237                } else if (Double.class.isAssignableFrom(type)) {
238                    return Double.NaN;
239                } else {
240                    // we cannot convert the NaN
241                    return Void.TYPE;
242                }
243            }
244    
245            // try to find a suitable type converter
246            TypeConverter converter = getOrFindTypeConverter(type, value);
247            if (converter != null) {
248                log.trace("Using converter: {} to convert {}", converter, key);
249                Object rc;
250                if (tryConvert) {
251                    rc = converter.tryConvertTo(type, exchange, value);
252                } else {
253                    rc = converter.convertTo(type, exchange, value);
254                }
255                if (rc != null) {
256                    return rc;
257                }
258            }
259    
260            // not found with that type then if it was a primitive type then try again with the wrapper type
261            if (type.isPrimitive()) {
262                Class<?> primitiveType = ObjectHelper.convertPrimitiveTypeToWrapperType(type);
263                if (primitiveType != type) {
264                    Class<?> fromType = value.getClass();
265                    TypeConverter tc = getOrFindTypeConverter(primitiveType, value);
266                    if (tc != null) {
267                        // add the type as a known type converter as we can convert from primitive to object converter
268                        addTypeConverter(type, fromType, tc);
269                        Object rc;
270                        if (tryConvert) {
271                            rc = tc.tryConvertTo(primitiveType, exchange, value);
272                        } else {
273                            rc = tc.convertTo(primitiveType, exchange, value);
274                        }
275                        if (rc != null) {
276                            return rc;
277                        }
278                    }
279                }
280            }
281    
282            // fallback converters
283            for (FallbackTypeConverter fallback : fallbackConverters) {
284                TypeConverter tc = fallback.getFallbackTypeConverter();
285                Object rc;
286                if (tryConvert) {
287                    rc = tc.tryConvertTo(type, exchange, value);
288                } else {
289                    rc = tc.convertTo(type, exchange, value);
290                }
291    
292                if (Void.TYPE.equals(rc)) {
293                    // it cannot be converted so give up
294                    return Void.TYPE;
295                }
296    
297                if (rc != null) {
298                    // if fallback can promote then let it be promoted to a first class type converter
299                    if (fallback.isCanPromote()) {
300                        // add it as a known type converter since we found a fallback that could do it
301                        if (log.isDebugEnabled()) {
302                            log.debug("Promoting fallback type converter as a known type converter to convert from: {} to: {} for the fallback converter: {}",
303                                    new Object[]{type.getCanonicalName(), value.getClass().getCanonicalName(), fallback.getFallbackTypeConverter()});
304                        }
305                        addTypeConverter(type, value.getClass(), fallback.getFallbackTypeConverter());
306                    }
307    
308                    if (log.isTraceEnabled()) {
309                        log.trace("Fallback type converter {} converted type from: {} to: {}",
310                                new Object[]{fallback.getFallbackTypeConverter(),
311                                    type.getCanonicalName(), value.getClass().getCanonicalName()});
312                    }
313    
314                    // return converted value
315                    return rc;
316                }
317            }
318    
319            if (!tryConvert) {
320                // Could not find suitable conversion, so remember it
321                // do not register misses for try conversions
322                misses.put(key, key);
323            }
324    
325            // Could not find suitable conversion, so return Void to indicate not found
326            return Void.TYPE;
327        }
328    
329        @Override
330        public void addTypeConverter(Class<?> toType, Class<?> fromType, TypeConverter typeConverter) {
331            log.trace("Adding type converter: {}", typeConverter);
332            TypeMapping key = new TypeMapping(toType, fromType);
333            TypeConverter converter = typeMappings.get(key);
334            // only override it if its different
335            // as race conditions can lead to many threads trying to promote the same fallback converter
336            if (typeConverter != converter) {
337                if (converter != null) {
338                    log.warn("Overriding type converter from: " + converter + " to: " + typeConverter);
339                }
340                typeMappings.put(key, typeConverter);
341                // remove any previous misses, as we added the new type converter
342                misses.remove(key);
343            }
344        }
345    
346        @Override
347        public void addFallbackTypeConverter(TypeConverter typeConverter, boolean canPromote) {
348            log.trace("Adding fallback type converter: {} which can promote: {}", typeConverter, canPromote);
349    
350            // add in top of fallback as the toString() fallback will nearly always be able to convert
351            fallbackConverters.add(0, new FallbackTypeConverter(typeConverter, canPromote));
352            if (typeConverter instanceof TypeConverterAware) {
353                TypeConverterAware typeConverterAware = (TypeConverterAware) typeConverter;
354                typeConverterAware.setTypeConverter(this);
355            }
356        }
357    
358        public TypeConverter getTypeConverter(Class<?> toType, Class<?> fromType) {
359            TypeMapping key = new TypeMapping(toType, fromType);
360            return typeMappings.get(key);
361        }
362    
363        @Override
364        public Injector getInjector() {
365            return injector;
366        }
367    
368        @Override
369        public void setInjector(Injector injector) {
370            this.injector = injector;
371        }
372    
373        public Set<Class<?>> getFromClassMappings() {
374            Set<Class<?>> answer = new HashSet<Class<?>>();
375            for (TypeMapping mapping : typeMappings.keySet()) {
376                answer.add(mapping.getFromType());
377            }
378            return answer;
379        }
380    
381        public Map<Class<?>, TypeConverter> getToClassMappings(Class<?> fromClass) {
382            Map<Class<?>, TypeConverter> answer = new HashMap<Class<?>, TypeConverter>();
383            for (Map.Entry<TypeMapping, TypeConverter> entry : typeMappings.entrySet()) {
384                TypeMapping mapping = entry.getKey();
385                if (mapping.isApplicable(fromClass)) {
386                    answer.put(mapping.getToType(), entry.getValue());
387                }
388            }
389            return answer;
390        }
391    
392        public Map<TypeMapping, TypeConverter> getTypeMappings() {
393            return typeMappings;
394        }
395    
396        protected <T> TypeConverter getOrFindTypeConverter(Class<?> toType, Object value) {
397            Class<?> fromType = null;
398            if (value != null) {
399                fromType = value.getClass();
400            }
401            TypeMapping key = new TypeMapping(toType, fromType);
402            TypeConverter converter = typeMappings.get(key);
403            if (converter == null) {
404                // converter not found, try to lookup then
405                converter = lookup(toType, fromType);
406                if (converter != null) {
407                    typeMappings.putIfAbsent(key, converter);
408                }
409            }
410            return converter;
411        }
412    
413        @Override
414        public TypeConverter lookup(Class<?> toType, Class<?> fromType) {
415            return doLookup(toType, fromType, false);
416        }
417    
418        protected TypeConverter doLookup(Class<?> toType, Class<?> fromType, boolean isSuper) {
419    
420            if (fromType != null) {
421                // lets try if there is a direct match
422                TypeConverter converter = getTypeConverter(toType, fromType);
423                if (converter != null) {
424                    return converter;
425                }
426    
427                // try the interfaces
428                for (Class<?> type : fromType.getInterfaces()) {
429                    converter = getTypeConverter(toType, type);
430                    if (converter != null) {
431                        return converter;
432                    }
433                }
434    
435                // try super then
436                Class<?> fromSuperClass = fromType.getSuperclass();
437                if (fromSuperClass != null && !fromSuperClass.equals(Object.class)) {
438                    converter = doLookup(toType, fromSuperClass, true);
439                    if (converter != null) {
440                        return converter;
441                    }
442                }
443            }
444    
445            // only do these tests as fallback and only on the target type (eg not on its super)
446            if (!isSuper) {
447                if (fromType != null && !fromType.equals(Object.class)) {
448    
449                    // lets try classes derived from this toType
450                    Set<Map.Entry<TypeMapping, TypeConverter>> entries = typeMappings.entrySet();
451                    for (Map.Entry<TypeMapping, TypeConverter> entry : entries) {
452                        TypeMapping key = entry.getKey();
453                        Class<?> aToType = key.getToType();
454                        if (toType.isAssignableFrom(aToType)) {
455                            Class<?> aFromType = key.getFromType();
456                            // skip Object based we do them last
457                            if (!aFromType.equals(Object.class) && aFromType.isAssignableFrom(fromType)) {
458                                return entry.getValue();
459                            }
460                        }
461                    }
462    
463                    // lets test for Object based converters as last resort
464                    TypeConverter converter = getTypeConverter(toType, Object.class);
465                    if (converter != null) {
466                        return converter;
467                    }
468                }
469            }
470    
471            // none found
472            return null;
473        }
474    
475        /**
476         * Loads the core type converters which is mandatory to use Camel
477         */
478        public void loadCoreTypeConverters() throws Exception {
479            // load all the type converters from camel-core
480            CoreTypeConverterLoader core = new CoreTypeConverterLoader();
481            core.load(this);
482        }
483    
484        /**
485         * Checks if the registry is loaded and if not lazily load it
486         */
487        protected void loadTypeConverters() throws Exception {
488            for (TypeConverterLoader typeConverterLoader : getTypeConverterLoaders()) {
489                typeConverterLoader.load(this);
490            }
491    
492            // lets try load any other fallback converters
493            try {
494                loadFallbackTypeConverters();
495            } catch (NoFactoryAvailableException e) {
496                // ignore its fine to have none
497            }
498        }
499    
500        protected void loadFallbackTypeConverters() throws IOException, ClassNotFoundException {
501            List<TypeConverter> converters = factoryFinder.newInstances("FallbackTypeConverter", getInjector(), TypeConverter.class);
502            for (TypeConverter converter : converters) {
503                addFallbackTypeConverter(converter, false);
504            }
505        }
506    
507        @Override
508        public Statistics getStatistics() {
509            return statistics;
510        }
511    
512        @Override
513        protected void doStart() throws Exception {
514            // noop
515        }
516    
517        @Override
518        protected void doStop() throws Exception {
519            // log utilization statistics when stopping, including mappings
520            String info = statistics.toString();
521            info += String.format(" mappings[total=%s, misses=%s]", typeMappings.size(), misses.size());
522            log.info(info);
523    
524            typeMappings.clear();
525            misses.clear();
526            statistics.reset();
527        }
528    
529        /**
530         * Represents utilization statistics
531         */
532        private final class UtilizationStatistics implements Statistics {
533    
534            @Override
535            public long getAttemptCounter() {
536                return attemptCounter.get();
537            }
538    
539            @Override
540            public long getHitCounter() {
541                return hitCounter.get();
542            }
543    
544            @Override
545            public long getMissCounter() {
546                return missCounter.get();
547            }
548    
549            @Override
550            public long getFailedCounter() {
551                return failedCounter.get();
552            }
553    
554            @Override
555            public void reset() {
556                attemptCounter.set(0);
557                hitCounter.set(0);
558                missCounter.set(0);
559                failedCounter.set(0);
560            }
561    
562            @Override
563            public String toString() {
564                return String.format("TypeConverterRegistry utilization[attempts=%s, hits=%s, misses=%s, failures=%s]",
565                        getAttemptCounter(), getHitCounter(), getMissCounter(), getFailedCounter());
566            }
567        }
568    
569        /**
570         * Represents a mapping from one type (which can be null) to another
571         */
572        protected static class TypeMapping {
573            private final Class<?> toType;
574            private final Class<?> fromType;
575    
576            TypeMapping(Class<?> toType, Class<?> fromType) {
577                this.toType = toType;
578                this.fromType = fromType;
579            }
580    
581            public Class<?> getFromType() {
582                return fromType;
583            }
584    
585            public Class<?> getToType() {
586                return toType;
587            }
588    
589            @Override
590            public boolean equals(Object object) {
591                if (object instanceof TypeMapping) {
592                    TypeMapping that = (TypeMapping) object;
593                    return ObjectHelper.equal(this.fromType, that.fromType)
594                            && ObjectHelper.equal(this.toType, that.toType);
595                }
596                return false;
597            }
598    
599            @Override
600            public int hashCode() {
601                int answer = toType.hashCode();
602                if (fromType != null) {
603                    answer *= 37 + fromType.hashCode();
604                }
605                return answer;
606            }
607    
608            @Override
609            public String toString() {
610                return "[" + fromType + "=>" + toType + "]";
611            }
612    
613            public boolean isApplicable(Class<?> fromClass) {
614                return fromType.isAssignableFrom(fromClass);
615            }
616        }
617    
618        /**
619         * Represents a fallback type converter
620         */
621        protected static class FallbackTypeConverter {
622            private final boolean canPromote;
623            private final TypeConverter fallbackTypeConverter;
624    
625            FallbackTypeConverter(TypeConverter fallbackTypeConverter, boolean canPromote) {
626                this.canPromote = canPromote;
627                this.fallbackTypeConverter = fallbackTypeConverter;
628            }
629    
630            public boolean isCanPromote() {
631                return canPromote;
632            }
633    
634            public TypeConverter getFallbackTypeConverter() {
635                return fallbackTypeConverter;
636            }
637        }
638    }