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 }