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