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