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}