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
028 import org.apache.camel.CamelExecutionException;
029 import org.apache.camel.Exchange;
030 import org.apache.camel.NoFactoryAvailableException;
031 import org.apache.camel.NoTypeConversionAvailableException;
032 import org.apache.camel.TypeConverter;
033 import org.apache.camel.spi.FactoryFinder;
034 import org.apache.camel.spi.Injector;
035 import org.apache.camel.spi.PackageScanClassResolver;
036 import org.apache.camel.spi.TypeConverterAware;
037 import org.apache.camel.spi.TypeConverterRegistry;
038 import org.apache.camel.util.ObjectHelper;
039 import org.apache.commons.logging.Log;
040 import org.apache.commons.logging.LogFactory;
041 import static org.apache.camel.util.ObjectHelper.wrapRuntimeCamelException;
042
043
044 /**
045 * Default implementation of a type converter registry used for
046 * <a href="http://camel.apache.org/type-converter.html">type converters</a> in Camel.
047 *
048 * @version $Revision: 772598 $
049 */
050 public class DefaultTypeConverter implements TypeConverter, TypeConverterRegistry {
051 private static final transient Log LOG = LogFactory.getLog(DefaultTypeConverter.class);
052 private final Map<TypeMapping, TypeConverter> typeMappings = new ConcurrentHashMap<TypeMapping, TypeConverter>();
053 private final Map<TypeMapping, TypeMapping> misses = new ConcurrentHashMap<TypeMapping, TypeMapping>();
054 private final List<TypeConverterLoader> typeConverterLoaders = new ArrayList<TypeConverterLoader>();
055 private final List<TypeConverter> fallbackConverters = new ArrayList<TypeConverter>();
056 private Injector injector;
057 private final FactoryFinder factoryFinder;
058 private boolean loaded;
059
060 public DefaultTypeConverter(PackageScanClassResolver resolver, Injector injector, FactoryFinder factoryFinder) {
061 this.injector = injector;
062 this.factoryFinder = factoryFinder;
063
064 typeConverterLoaders.add(new AnnotationTypeConverterLoader(resolver));
065
066 // add to string first as it will then be last in the last as to string can nearly
067 // always convert something to a string so we want it only as the last resort
068 addFallbackTypeConverter(new ToStringTypeConverter());
069 addFallbackTypeConverter(new EnumTypeConverter());
070 addFallbackTypeConverter(new ArrayTypeConverter());
071 addFallbackTypeConverter(new PropertyEditorTypeConverter());
072 addFallbackTypeConverter(new FutureTypeConverter(this));
073 }
074
075 public List<TypeConverterLoader> getTypeConverterLoaders() {
076 return typeConverterLoaders;
077 }
078
079 public <T> T convertTo(Class<T> type, Object value) {
080 return convertTo(type, null, value);
081 }
082
083 public <T> T convertTo(Class<T> type, Exchange exchange, Object value) {
084 Object answer;
085 try {
086 answer = doConvertTo(type, exchange, value);
087 } catch (CamelExecutionException e) {
088 // rethrow exception exception as its not due to failed convertion
089 throw e;
090 } catch (Exception e) {
091 // we cannot convert so return null
092 if (LOG.isDebugEnabled()) {
093 LOG.debug(NoTypeConversionAvailableException.createMessage(value, type)
094 + " Caused by: " + e.getMessage() + ". Will ignore this and continue.");
095 }
096 return null;
097 }
098 if (answer == Void.TYPE) {
099 // Could not find suitable conversion
100 return null;
101 } else {
102 return (T) answer;
103 }
104 }
105
106 public <T> T mandatoryConvertTo(Class<T> type, Object value) throws NoTypeConversionAvailableException {
107 return mandatoryConvertTo(type, null, value);
108 }
109
110 public <T> T mandatoryConvertTo(Class<T> type, Exchange exchange, Object value) throws NoTypeConversionAvailableException {
111 Object answer;
112 try {
113 answer = doConvertTo(type, exchange, value);
114 } catch (Exception e) {
115 throw new NoTypeConversionAvailableException(value, type, e);
116 }
117 if (answer == Void.TYPE) {
118 // Could not find suitable conversion
119 throw new NoTypeConversionAvailableException(value, type);
120 } else {
121 return (T) answer;
122 }
123 }
124
125 @SuppressWarnings("unchecked")
126 public Object doConvertTo(final Class type, final Exchange exchange, final Object value) {
127 if (LOG.isTraceEnabled()) {
128 LOG.trace("Converting " + (value == null ? "null" : value.getClass().getCanonicalName())
129 + " -> " + type.getCanonicalName() + " with value: " + value);
130 }
131
132 if (value == null) {
133 // lets avoid NullPointerException when converting to boolean for null values
134 if (boolean.class.isAssignableFrom(type)) {
135 return Boolean.FALSE;
136 }
137 return null;
138 }
139
140 // same instance type
141 if (type.isInstance(value)) {
142 return type.cast(value);
143 }
144
145 // check if we have tried it before and if its a miss
146 TypeMapping key = new TypeMapping(type, value.getClass());
147 if (misses.containsKey(key)) {
148 // we have tried before but we cannot convert this one
149 return Void.TYPE;
150 }
151
152 // make sure we have loaded the converters
153 checkLoaded();
154
155 // try to find a suitable type converter
156 TypeConverter converter = getOrFindTypeConverter(type, value);
157 if (converter != null) {
158 Object rc = converter.convertTo(type, exchange, value);
159 if (rc != null) {
160 return rc;
161 }
162 }
163
164 // fallback converters
165 for (TypeConverter fallback : fallbackConverters) {
166 Object rc = fallback.convertTo(type, exchange, value);
167 if (rc != null) {
168 return rc;
169 }
170 }
171
172 // primitives
173 if (type.isPrimitive()) {
174 Class primitiveType = ObjectHelper.convertPrimitiveTypeToWrapperType(type);
175 if (primitiveType != type) {
176 return convertTo(primitiveType, exchange, value);
177 }
178 }
179
180 // Could not find suitable conversion, so remember it
181 synchronized (misses) {
182 misses.put(key, key);
183 }
184
185 // Could not find suitable conversion, so return Void to indicate not found
186 return Void.TYPE;
187 }
188
189 public void addTypeConverter(Class toType, Class fromType, TypeConverter typeConverter) {
190 if (LOG.isTraceEnabled()) {
191 LOG.trace("Adding type converter: " + typeConverter);
192 }
193 TypeMapping key = new TypeMapping(toType, fromType);
194 synchronized (typeMappings) {
195 TypeConverter converter = typeMappings.get(key);
196 if (converter != null) {
197 LOG.warn("Overriding type converter from: " + converter + " to: " + typeConverter);
198 }
199 typeMappings.put(key, typeConverter);
200 }
201 }
202
203 public void addFallbackTypeConverter(TypeConverter typeConverter) {
204 if (LOG.isTraceEnabled()) {
205 LOG.trace("Adding fallback type converter: " + typeConverter);
206 }
207
208 // add in top of fallback as the toString() fallback will nearly always be able to convert
209 fallbackConverters.add(0, typeConverter);
210 if (typeConverter instanceof TypeConverterAware) {
211 TypeConverterAware typeConverterAware = (TypeConverterAware)typeConverter;
212 typeConverterAware.setTypeConverter(this);
213 }
214 }
215
216 public TypeConverter getTypeConverter(Class toType, Class fromType) {
217 TypeMapping key = new TypeMapping(toType, fromType);
218 return typeMappings.get(key);
219 }
220
221 public Injector getInjector() {
222 return injector;
223 }
224
225 public void setInjector(Injector injector) {
226 this.injector = injector;
227 }
228
229 public Set<Class> getFromClassMappings() {
230 // make sure we have loaded the converters
231 checkLoaded();
232
233 Set<Class> answer = new HashSet<Class>();
234 synchronized (typeMappings) {
235 for (TypeMapping mapping : typeMappings.keySet()) {
236 answer.add(mapping.getFromType());
237 }
238 }
239 return answer;
240 }
241
242 public Map<Class, TypeConverter> getToClassMappings(Class fromClass) {
243 Map<Class, TypeConverter> answer = new HashMap<Class, TypeConverter>();
244 synchronized (typeMappings) {
245 for (Map.Entry<TypeMapping, TypeConverter> entry : typeMappings.entrySet()) {
246 TypeMapping mapping = entry.getKey();
247 if (mapping.isApplicable(fromClass)) {
248 answer.put(mapping.getToType(), entry.getValue());
249 }
250 }
251 }
252 return answer;
253 }
254
255 public Map<TypeMapping, TypeConverter> getTypeMappings() {
256 // make sure we have loaded the converters
257 checkLoaded();
258
259 return typeMappings;
260 }
261
262 protected <T> TypeConverter getOrFindTypeConverter(Class toType, Object value) {
263 Class fromType = null;
264 if (value != null) {
265 fromType = value.getClass();
266 }
267 TypeMapping key = new TypeMapping(toType, fromType);
268 TypeConverter converter;
269 synchronized (typeMappings) {
270 converter = typeMappings.get(key);
271 if (converter == null) {
272 converter = lookup(toType, fromType);
273 if (converter != null) {
274 typeMappings.put(key, converter);
275 }
276 }
277 }
278 return converter;
279 }
280
281 public TypeConverter lookup(Class toType, Class fromType) {
282 // make sure we have loaded the converters
283 checkLoaded();
284
285 return doLookup(toType, fromType, false);
286 }
287
288 private TypeConverter doLookup(Class toType, Class fromType, boolean isSuper) {
289
290 if (fromType != null) {
291 // lets try if there is a direct match
292 TypeConverter converter = getTypeConverter(toType, fromType);
293 if (converter != null) {
294 return converter;
295 }
296
297 // try the interfaces
298 for (Class type : fromType.getInterfaces()) {
299 converter = getTypeConverter(toType, type);
300 if (converter != null) {
301 return converter;
302 }
303 }
304
305 // try super then
306 Class fromSuperClass = fromType.getSuperclass();
307 if (fromSuperClass != null && !fromSuperClass.equals(Object.class)) {
308 converter = doLookup(toType, fromSuperClass, true);
309 if (converter != null) {
310 return converter;
311 }
312 }
313 }
314
315 // only do these tests as fallback and only on the target type (eg not on its super)
316 if (!isSuper) {
317 if (fromType != null && !fromType.equals(Object.class)) {
318
319 // lets try classes derived from this toType
320 Set<Map.Entry<TypeMapping, TypeConverter>> entries = typeMappings.entrySet();
321 for (Map.Entry<TypeMapping, TypeConverter> entry : entries) {
322 TypeMapping key = entry.getKey();
323 Class aToType = key.getToType();
324 if (toType.isAssignableFrom(aToType)) {
325 Class aFromType = key.getFromType();
326 // skip Object based we do them last
327 if (!aFromType.equals(Object.class) && aFromType.isAssignableFrom(fromType)) {
328 return entry.getValue();
329 }
330 }
331 }
332
333 // lets test for Object based converters as last resort
334 TypeConverter converter = getTypeConverter(toType, Object.class);
335 if (converter != null) {
336 return converter;
337 }
338 }
339 }
340
341 // none found
342 return null;
343 }
344
345 /**
346 * Checks if the registry is loaded and if not lazily load it
347 */
348 protected synchronized void checkLoaded() {
349 if (!loaded) {
350 loaded = true;
351 try {
352 for (TypeConverterLoader typeConverterLoader : typeConverterLoaders) {
353 typeConverterLoader.load(this);
354 }
355
356 // lets try load any other fallback converters
357 try {
358 loadFallbackTypeConverters();
359 } catch (NoFactoryAvailableException e) {
360 // ignore its fine to have none
361 }
362 } catch (Exception e) {
363 throw wrapRuntimeCamelException(e);
364 }
365 }
366 }
367
368 protected void loadFallbackTypeConverters() throws IOException, ClassNotFoundException {
369 List<TypeConverter> converters = factoryFinder.newInstances("FallbackTypeConverter", getInjector(), TypeConverter.class);
370 for (TypeConverter converter : converters) {
371 addFallbackTypeConverter(converter);
372 }
373 }
374
375 /**
376 * Represents a mapping from one type (which can be null) to another
377 */
378 protected static class TypeMapping {
379 Class toType;
380 Class fromType;
381
382 public TypeMapping(Class toType, Class fromType) {
383 this.toType = toType;
384 this.fromType = fromType;
385 }
386
387 public Class getFromType() {
388 return fromType;
389 }
390
391 public Class getToType() {
392 return toType;
393 }
394
395 @Override
396 public boolean equals(Object object) {
397 if (object instanceof TypeMapping) {
398 TypeMapping that = (TypeMapping)object;
399 return ObjectHelper.equal(this.fromType, that.fromType)
400 && ObjectHelper.equal(this.toType, that.toType);
401 }
402 return false;
403 }
404
405 @Override
406 public int hashCode() {
407 int answer = toType.hashCode();
408 if (fromType != null) {
409 answer *= 37 + fromType.hashCode();
410 }
411 return answer;
412 }
413
414 @Override
415 public String toString() {
416 return "[" + fromType + "=>" + toType + "]";
417 }
418
419 public boolean isApplicable(Class fromClass) {
420 return fromType.isAssignableFrom(fromClass);
421 }
422 }
423 }