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<TypeMapping, TypeConverter>(); 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<TypeConverterLoader>(); 074 protected final List<FallbackTypeConverter> fallbackConverters = new CopyOnWriteArrayList<FallbackTypeConverter>(); 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<Class<?>>(); 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<Class<?>, TypeConverter>(); 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<Class<?>[]>(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}