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 transient 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 attemptCounter.incrementAndGet(); 111 answer = doConvertTo(type, exchange, value, false); 112 } catch (Exception e) { 113 failedCounter.incrementAndGet(); 114 // if its a ExecutionException then we have rethrow it as its not due to failed conversion 115 // this is special for FutureTypeConverter 116 boolean execution = ObjectHelper.getException(ExecutionException.class, e) != null 117 || ObjectHelper.getException(CamelExecutionException.class, e) != null; 118 if (execution) { 119 throw ObjectHelper.wrapCamelExecutionException(exchange, e); 120 } 121 122 // error occurred during type conversion 123 if (e instanceof TypeConversionException) { 124 throw (TypeConversionException) e; 125 } else { 126 throw new TypeConversionException(value, type, e); 127 } 128 } 129 if (answer == Void.TYPE) { 130 // Could not find suitable conversion 131 missCounter.incrementAndGet(); 132 // Could not find suitable conversion 133 return null; 134 } else { 135 hitCounter.incrementAndGet(); 136 return (T) answer; 137 } 138 } 139 140 @Override 141 public <T> T mandatoryConvertTo(Class<T> type, Object value) throws NoTypeConversionAvailableException { 142 return mandatoryConvertTo(type, null, value); 143 } 144 145 @SuppressWarnings("unchecked") 146 @Override 147 public <T> T mandatoryConvertTo(Class<T> type, Exchange exchange, Object value) throws NoTypeConversionAvailableException { 148 if (!isRunAllowed()) { 149 throw new IllegalStateException(this + " is not started"); 150 } 151 152 Object answer; 153 try { 154 attemptCounter.incrementAndGet(); 155 answer = doConvertTo(type, exchange, value, false); 156 } catch (Exception e) { 157 failedCounter.incrementAndGet(); 158 // error occurred during type conversion 159 if (e instanceof TypeConversionException) { 160 throw (TypeConversionException) e; 161 } else { 162 throw new TypeConversionException(value, type, e); 163 } 164 } 165 if (answer == Void.TYPE || value == null) { 166 // Could not find suitable conversion 167 missCounter.incrementAndGet(); 168 // Could not find suitable conversion 169 throw new NoTypeConversionAvailableException(value, type); 170 } else { 171 hitCounter.incrementAndGet(); 172 return (T) answer; 173 } 174 } 175 176 @Override 177 public <T> T tryConvertTo(Class<T> type, Object value) { 178 return tryConvertTo(type, null, value); 179 } 180 181 @SuppressWarnings("unchecked") 182 @Override 183 public <T> T tryConvertTo(Class<T> type, Exchange exchange, Object value) { 184 if (!isRunAllowed()) { 185 return null; 186 } 187 188 Object answer; 189 try { 190 attemptCounter.incrementAndGet(); 191 answer = doConvertTo(type, exchange, value, true); 192 } catch (Exception e) { 193 failedCounter.incrementAndGet(); 194 return null; 195 } 196 if (answer == Void.TYPE) { 197 missCounter.incrementAndGet(); 198 // Could not find suitable conversion 199 return null; 200 } else { 201 hitCounter.incrementAndGet(); 202 return (T) answer; 203 } 204 } 205 206 protected Object doConvertTo(final Class<?> type, final Exchange exchange, final Object value, final boolean tryConvert) { 207 if (log.isTraceEnabled()) { 208 log.trace("Converting {} -> {} with value: {}", 209 new Object[]{value == null ? "null" : value.getClass().getCanonicalName(), 210 type.getCanonicalName(), value}); 211 } 212 213 if (value == null) { 214 // lets avoid NullPointerException when converting to boolean for null values 215 if (boolean.class.isAssignableFrom(type)) { 216 return Boolean.FALSE; 217 } 218 return null; 219 } 220 221 // same instance type 222 if (type.isInstance(value)) { 223 return type.cast(value); 224 } 225 226 // check if we have tried it before and if its a miss 227 TypeMapping key = new TypeMapping(type, value.getClass()); 228 if (misses.containsKey(key)) { 229 // we have tried before but we cannot convert this one 230 return Void.TYPE; 231 } 232 233 // special for NaN numbers, which we can only convert for floating numbers 234 if (ObjectHelper.isNaN(value)) { 235 if (Float.class.isAssignableFrom(type)) { 236 return Float.NaN; 237 } else if (Double.class.isAssignableFrom(type)) { 238 return Double.NaN; 239 } else { 240 // we cannot convert the NaN 241 return Void.TYPE; 242 } 243 } 244 245 // try to find a suitable type converter 246 TypeConverter converter = getOrFindTypeConverter(type, value); 247 if (converter != null) { 248 log.trace("Using converter: {} to convert {}", converter, key); 249 Object rc; 250 if (tryConvert) { 251 rc = converter.tryConvertTo(type, exchange, value); 252 } else { 253 rc = converter.convertTo(type, exchange, value); 254 } 255 if (rc != null) { 256 return rc; 257 } 258 } 259 260 // not found with that type then if it was a primitive type then try again with the wrapper type 261 if (type.isPrimitive()) { 262 Class<?> primitiveType = ObjectHelper.convertPrimitiveTypeToWrapperType(type); 263 if (primitiveType != type) { 264 Class<?> fromType = value.getClass(); 265 TypeConverter tc = getOrFindTypeConverter(primitiveType, value); 266 if (tc != null) { 267 // add the type as a known type converter as we can convert from primitive to object converter 268 addTypeConverter(type, fromType, tc); 269 Object rc; 270 if (tryConvert) { 271 rc = tc.tryConvertTo(primitiveType, exchange, value); 272 } else { 273 rc = tc.convertTo(primitiveType, exchange, value); 274 } 275 if (rc != null) { 276 return rc; 277 } 278 } 279 } 280 } 281 282 // fallback converters 283 for (FallbackTypeConverter fallback : fallbackConverters) { 284 TypeConverter tc = fallback.getFallbackTypeConverter(); 285 Object rc; 286 if (tryConvert) { 287 rc = tc.tryConvertTo(type, exchange, value); 288 } else { 289 rc = tc.convertTo(type, exchange, value); 290 } 291 292 if (Void.TYPE.equals(rc)) { 293 // it cannot be converted so give up 294 return Void.TYPE; 295 } 296 297 if (rc != null) { 298 // if fallback can promote then let it be promoted to a first class type converter 299 if (fallback.isCanPromote()) { 300 // add it as a known type converter since we found a fallback that could do it 301 if (log.isDebugEnabled()) { 302 log.debug("Promoting fallback type converter as a known type converter to convert from: {} to: {} for the fallback converter: {}", 303 new Object[]{type.getCanonicalName(), value.getClass().getCanonicalName(), fallback.getFallbackTypeConverter()}); 304 } 305 addTypeConverter(type, value.getClass(), fallback.getFallbackTypeConverter()); 306 } 307 308 if (log.isTraceEnabled()) { 309 log.trace("Fallback type converter {} converted type from: {} to: {}", 310 new Object[]{fallback.getFallbackTypeConverter(), 311 type.getCanonicalName(), value.getClass().getCanonicalName()}); 312 } 313 314 // return converted value 315 return rc; 316 } 317 } 318 319 if (!tryConvert) { 320 // Could not find suitable conversion, so remember it 321 // do not register misses for try conversions 322 misses.put(key, key); 323 } 324 325 // Could not find suitable conversion, so return Void to indicate not found 326 return Void.TYPE; 327 } 328 329 @Override 330 public void addTypeConverter(Class<?> toType, Class<?> fromType, TypeConverter typeConverter) { 331 log.trace("Adding type converter: {}", typeConverter); 332 TypeMapping key = new TypeMapping(toType, fromType); 333 TypeConverter converter = typeMappings.get(key); 334 // only override it if its different 335 // as race conditions can lead to many threads trying to promote the same fallback converter 336 if (typeConverter != converter) { 337 if (converter != null) { 338 log.warn("Overriding type converter from: " + converter + " to: " + typeConverter); 339 } 340 typeMappings.put(key, typeConverter); 341 // remove any previous misses, as we added the new type converter 342 misses.remove(key); 343 } 344 } 345 346 @Override 347 public void addFallbackTypeConverter(TypeConverter typeConverter, boolean canPromote) { 348 log.trace("Adding fallback type converter: {} which can promote: {}", typeConverter, canPromote); 349 350 // add in top of fallback as the toString() fallback will nearly always be able to convert 351 fallbackConverters.add(0, new FallbackTypeConverter(typeConverter, canPromote)); 352 if (typeConverter instanceof TypeConverterAware) { 353 TypeConverterAware typeConverterAware = (TypeConverterAware) typeConverter; 354 typeConverterAware.setTypeConverter(this); 355 } 356 } 357 358 public TypeConverter getTypeConverter(Class<?> toType, Class<?> fromType) { 359 TypeMapping key = new TypeMapping(toType, fromType); 360 return typeMappings.get(key); 361 } 362 363 @Override 364 public Injector getInjector() { 365 return injector; 366 } 367 368 @Override 369 public void setInjector(Injector injector) { 370 this.injector = injector; 371 } 372 373 public Set<Class<?>> getFromClassMappings() { 374 Set<Class<?>> answer = new HashSet<Class<?>>(); 375 for (TypeMapping mapping : typeMappings.keySet()) { 376 answer.add(mapping.getFromType()); 377 } 378 return answer; 379 } 380 381 public Map<Class<?>, TypeConverter> getToClassMappings(Class<?> fromClass) { 382 Map<Class<?>, TypeConverter> answer = new HashMap<Class<?>, TypeConverter>(); 383 for (Map.Entry<TypeMapping, TypeConverter> entry : typeMappings.entrySet()) { 384 TypeMapping mapping = entry.getKey(); 385 if (mapping.isApplicable(fromClass)) { 386 answer.put(mapping.getToType(), entry.getValue()); 387 } 388 } 389 return answer; 390 } 391 392 public Map<TypeMapping, TypeConverter> getTypeMappings() { 393 return typeMappings; 394 } 395 396 protected <T> TypeConverter getOrFindTypeConverter(Class<?> toType, Object value) { 397 Class<?> fromType = null; 398 if (value != null) { 399 fromType = value.getClass(); 400 } 401 TypeMapping key = new TypeMapping(toType, fromType); 402 TypeConverter converter = typeMappings.get(key); 403 if (converter == null) { 404 // converter not found, try to lookup then 405 converter = lookup(toType, fromType); 406 if (converter != null) { 407 typeMappings.putIfAbsent(key, converter); 408 } 409 } 410 return converter; 411 } 412 413 @Override 414 public TypeConverter lookup(Class<?> toType, Class<?> fromType) { 415 return doLookup(toType, fromType, false); 416 } 417 418 protected TypeConverter doLookup(Class<?> toType, Class<?> fromType, boolean isSuper) { 419 420 if (fromType != null) { 421 // lets try if there is a direct match 422 TypeConverter converter = getTypeConverter(toType, fromType); 423 if (converter != null) { 424 return converter; 425 } 426 427 // try the interfaces 428 for (Class<?> type : fromType.getInterfaces()) { 429 converter = getTypeConverter(toType, type); 430 if (converter != null) { 431 return converter; 432 } 433 } 434 435 // try super then 436 Class<?> fromSuperClass = fromType.getSuperclass(); 437 if (fromSuperClass != null && !fromSuperClass.equals(Object.class)) { 438 converter = doLookup(toType, fromSuperClass, true); 439 if (converter != null) { 440 return converter; 441 } 442 } 443 } 444 445 // only do these tests as fallback and only on the target type (eg not on its super) 446 if (!isSuper) { 447 if (fromType != null && !fromType.equals(Object.class)) { 448 449 // lets try classes derived from this toType 450 Set<Map.Entry<TypeMapping, TypeConverter>> entries = typeMappings.entrySet(); 451 for (Map.Entry<TypeMapping, TypeConverter> entry : entries) { 452 TypeMapping key = entry.getKey(); 453 Class<?> aToType = key.getToType(); 454 if (toType.isAssignableFrom(aToType)) { 455 Class<?> aFromType = key.getFromType(); 456 // skip Object based we do them last 457 if (!aFromType.equals(Object.class) && aFromType.isAssignableFrom(fromType)) { 458 return entry.getValue(); 459 } 460 } 461 } 462 463 // lets test for Object based converters as last resort 464 TypeConverter converter = getTypeConverter(toType, Object.class); 465 if (converter != null) { 466 return converter; 467 } 468 } 469 } 470 471 // none found 472 return null; 473 } 474 475 /** 476 * Loads the core type converters which is mandatory to use Camel 477 */ 478 public void loadCoreTypeConverters() throws Exception { 479 // load all the type converters from camel-core 480 CoreTypeConverterLoader core = new CoreTypeConverterLoader(); 481 core.load(this); 482 } 483 484 /** 485 * Checks if the registry is loaded and if not lazily load it 486 */ 487 protected void loadTypeConverters() throws Exception { 488 for (TypeConverterLoader typeConverterLoader : getTypeConverterLoaders()) { 489 typeConverterLoader.load(this); 490 } 491 492 // lets try load any other fallback converters 493 try { 494 loadFallbackTypeConverters(); 495 } catch (NoFactoryAvailableException e) { 496 // ignore its fine to have none 497 } 498 } 499 500 protected void loadFallbackTypeConverters() throws IOException, ClassNotFoundException { 501 List<TypeConverter> converters = factoryFinder.newInstances("FallbackTypeConverter", getInjector(), TypeConverter.class); 502 for (TypeConverter converter : converters) { 503 addFallbackTypeConverter(converter, false); 504 } 505 } 506 507 @Override 508 public Statistics getStatistics() { 509 return statistics; 510 } 511 512 @Override 513 protected void doStart() throws Exception { 514 // noop 515 } 516 517 @Override 518 protected void doStop() throws Exception { 519 // log utilization statistics when stopping, including mappings 520 String info = statistics.toString(); 521 info += String.format(" mappings[total=%s, misses=%s]", typeMappings.size(), misses.size()); 522 log.info(info); 523 524 typeMappings.clear(); 525 misses.clear(); 526 statistics.reset(); 527 } 528 529 /** 530 * Represents utilization statistics 531 */ 532 private final class UtilizationStatistics implements Statistics { 533 534 @Override 535 public long getAttemptCounter() { 536 return attemptCounter.get(); 537 } 538 539 @Override 540 public long getHitCounter() { 541 return hitCounter.get(); 542 } 543 544 @Override 545 public long getMissCounter() { 546 return missCounter.get(); 547 } 548 549 @Override 550 public long getFailedCounter() { 551 return failedCounter.get(); 552 } 553 554 @Override 555 public void reset() { 556 attemptCounter.set(0); 557 hitCounter.set(0); 558 missCounter.set(0); 559 failedCounter.set(0); 560 } 561 562 @Override 563 public String toString() { 564 return String.format("TypeConverterRegistry utilization[attempts=%s, hits=%s, misses=%s, failures=%s]", 565 getAttemptCounter(), getHitCounter(), getMissCounter(), getFailedCounter()); 566 } 567 } 568 569 /** 570 * Represents a mapping from one type (which can be null) to another 571 */ 572 protected static class TypeMapping { 573 private final Class<?> toType; 574 private final Class<?> fromType; 575 576 TypeMapping(Class<?> toType, Class<?> fromType) { 577 this.toType = toType; 578 this.fromType = fromType; 579 } 580 581 public Class<?> getFromType() { 582 return fromType; 583 } 584 585 public Class<?> getToType() { 586 return toType; 587 } 588 589 @Override 590 public boolean equals(Object object) { 591 if (object instanceof TypeMapping) { 592 TypeMapping that = (TypeMapping) object; 593 return ObjectHelper.equal(this.fromType, that.fromType) 594 && ObjectHelper.equal(this.toType, that.toType); 595 } 596 return false; 597 } 598 599 @Override 600 public int hashCode() { 601 int answer = toType.hashCode(); 602 if (fromType != null) { 603 answer *= 37 + fromType.hashCode(); 604 } 605 return answer; 606 } 607 608 @Override 609 public String toString() { 610 return "[" + fromType + "=>" + toType + "]"; 611 } 612 613 public boolean isApplicable(Class<?> fromClass) { 614 return fromType.isAssignableFrom(fromClass); 615 } 616 } 617 618 /** 619 * Represents a fallback type converter 620 */ 621 protected static class FallbackTypeConverter { 622 private final boolean canPromote; 623 private final TypeConverter fallbackTypeConverter; 624 625 FallbackTypeConverter(TypeConverter fallbackTypeConverter, boolean canPromote) { 626 this.canPromote = canPromote; 627 this.fallbackTypeConverter = fallbackTypeConverter; 628 } 629 630 public boolean isCanPromote() { 631 return canPromote; 632 } 633 634 public TypeConverter getFallbackTypeConverter() { 635 return fallbackTypeConverter; 636 } 637 } 638 }