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