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