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