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