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