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