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: 52365 $
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 Injector injector;
050 private List<TypeConverterLoader> typeConverterLoaders = new ArrayList<TypeConverterLoader>();
051 private List<TypeConverter> fallbackConverters = new ArrayList<TypeConverter>();
052 private boolean loaded;
053
054 public DefaultTypeConverter(Injector injector) {
055 typeConverterLoaders.add(new AnnotationTypeConverterLoader());
056 this.injector = injector;
057 addFallbackConverter(new AsyncProcessorTypeConverter());
058 addFallbackConverter(new PropertyEditorTypeConverter());
059 addFallbackConverter(new ToStringTypeConverter());
060 addFallbackConverter(new ArrayTypeConverter());
061 addFallbackConverter(new EnumTypeConverter());
062 }
063
064 public <T> T convertTo(Class<T> type, Object value) {
065 return convertTo(type, null, value);
066 }
067
068 @SuppressWarnings("unchecked")
069 public <T> T convertTo(Class<T> type, Exchange exchange, Object value) {
070 if (LOG.isTraceEnabled()) {
071 LOG.trace("Converting " + (value == null ? "null" : value.getClass().getCanonicalName())
072 + " -> " + type.getCanonicalName() + " with value: " + value);
073 }
074
075 if (value == null) {
076 // lets avoid NullPointerException when converting to boolean for null values
077 if (boolean.class.isAssignableFrom(type)) {
078 return (T) Boolean.FALSE;
079 }
080 return null;
081 }
082
083 // same instance type
084 if (type.isInstance(value)) {
085 return type.cast(value);
086 }
087
088 // make sure we have loaded the converters
089 checkLoaded();
090
091 // try to find a suitable type converter
092 TypeConverter converter = getOrFindTypeConverter(type, value);
093 if (converter != null) {
094 T rc = converter.convertTo(type, exchange, value);
095 if (rc != null) {
096 return rc;
097 }
098 }
099
100 // fallback converters
101 for (TypeConverter fallback : fallbackConverters) {
102 T rc = fallback.convertTo(type, exchange, value);
103 if (rc != null) {
104 return rc;
105 }
106 }
107
108 // primitives
109 if (type.isPrimitive()) {
110 Class primitiveType = ObjectHelper.convertPrimitiveTypeToWrapperType(type);
111 if (primitiveType != type) {
112 return (T) convertTo(primitiveType, exchange, value);
113 }
114 }
115
116 // Could not find suitable conversion
117 throw new NoTypeConversionAvailableException(value, type);
118 }
119
120 public void addTypeConverter(Class toType, Class fromType, TypeConverter typeConverter) {
121 TypeMapping key = new TypeMapping(toType, fromType);
122 synchronized (typeMappings) {
123 TypeConverter converter = typeMappings.get(key);
124 if (converter != null) {
125 LOG.warn("Overriding type converter from: " + converter + " to: " + typeConverter);
126 }
127 typeMappings.put(key, typeConverter);
128 }
129 }
130
131 public void addFallbackConverter(TypeConverter converter) {
132 fallbackConverters.add(converter);
133 if (converter instanceof TypeConverterAware) {
134 TypeConverterAware typeConverterAware = (TypeConverterAware)converter;
135 typeConverterAware.setTypeConverter(this);
136 }
137 }
138
139 public TypeConverter getTypeConverter(Class toType, Class fromType) {
140 TypeMapping key = new TypeMapping(toType, fromType);
141 return typeMappings.get(key);
142 }
143
144 public Injector getInjector() {
145 return injector;
146 }
147
148 public void setInjector(Injector injector) {
149 this.injector = injector;
150 }
151
152 protected <T> TypeConverter getOrFindTypeConverter(Class toType, Object value) {
153 Class fromType = null;
154 if (value != null) {
155 fromType = value.getClass();
156 }
157 TypeMapping key = new TypeMapping(toType, fromType);
158 TypeConverter converter;
159 synchronized (typeMappings) {
160 converter = typeMappings.get(key);
161 if (converter == null) {
162 converter = findTypeConverter(toType, fromType, value);
163 if (converter != null) {
164 typeMappings.put(key, converter);
165 }
166 }
167 }
168 return converter;
169 }
170
171 /**
172 * Tries to auto-discover any available type converters
173 */
174 protected TypeConverter findTypeConverter(Class toType, Class fromType, Object value) {
175 // lets try the super classes of the from type
176 if (fromType != null) {
177 Class fromSuperClass = fromType.getSuperclass();
178 if (fromSuperClass != null && !fromSuperClass.equals(Object.class)) {
179
180 TypeConverter converter = getTypeConverter(toType, fromSuperClass);
181 if (converter == null) {
182 converter = findTypeConverter(toType, fromSuperClass, value);
183 }
184 if (converter != null) {
185 return converter;
186 }
187 }
188 for (Class type : fromType.getInterfaces()) {
189 TypeConverter converter = getTypeConverter(toType, type);
190 if (converter != null) {
191 return converter;
192 }
193 }
194
195 // lets test for arrays
196 if (fromType.isArray() && !fromType.getComponentType().isPrimitive()) {
197 // TODO can we try walking the inheritance-tree for the element types?
198 if (!fromType.equals(Object[].class)) {
199 fromSuperClass = Object[].class;
200
201 TypeConverter converter = getTypeConverter(toType, fromSuperClass);
202 if (converter == null) {
203 converter = findTypeConverter(toType, fromSuperClass, value);
204 }
205 if (converter != null) {
206 return converter;
207 }
208 }
209 }
210
211 // lets test for Object based converters
212 if (!fromType.equals(Object.class)) {
213 TypeConverter converter = getTypeConverter(toType, Object.class);
214 if (converter != null) {
215 return converter;
216 }
217 }
218 }
219
220 // lets try classes derived from this toType
221 if (fromType != null) {
222 Set<Map.Entry<TypeMapping, TypeConverter>> entries = typeMappings.entrySet();
223 for (Map.Entry<TypeMapping, TypeConverter> entry : entries) {
224 TypeMapping key = entry.getKey();
225 Class aToType = key.getToType();
226 if (toType.isAssignableFrom(aToType)) {
227 if (key.getFromType().isAssignableFrom(fromType)) {
228 return entry.getValue();
229 }
230 }
231 }
232 }
233
234 // TODO look at constructors of toType?
235 return null;
236 }
237
238 /**
239 * Checks if the registry is loaded and if not lazily load it
240 */
241 protected synchronized void checkLoaded() {
242 if (!loaded) {
243 loaded = true;
244 try {
245 for (TypeConverterLoader typeConverterLoader : typeConverterLoaders) {
246 typeConverterLoader.load(this);
247 }
248
249 // lets try load any other fallback converters
250 try {
251 loadFallbackTypeConverters();
252 } catch (NoFactoryAvailableException e) {
253 // ignore its fine to have none
254 }
255 } catch (Exception e) {
256 throw wrapRuntimeCamelException(e);
257 }
258 }
259 }
260
261 protected void loadFallbackTypeConverters() throws IOException, ClassNotFoundException {
262 FactoryFinder finder = new FactoryFinder();
263 List<TypeConverter> converters = finder.newInstances("FallbackTypeConverter", getInjector(),
264 TypeConverter.class);
265 for (TypeConverter converter : converters) {
266 addFallbackConverter(converter);
267 }
268 }
269
270 /**
271 * Represents a mapping from one type (which can be null) to another
272 */
273 protected static class TypeMapping {
274 Class toType;
275 Class fromType;
276
277 public TypeMapping(Class toType, Class fromType) {
278 this.toType = toType;
279 this.fromType = fromType;
280 }
281
282 public Class getFromType() {
283 return fromType;
284 }
285
286 public Class getToType() {
287 return toType;
288 }
289
290 @Override
291 public boolean equals(Object object) {
292 if (object instanceof TypeMapping) {
293 TypeMapping that = (TypeMapping)object;
294 return ObjectHelper.equal(this.fromType, that.fromType)
295 && ObjectHelper.equal(this.toType, that.toType);
296 }
297 return false;
298 }
299
300 @Override
301 public int hashCode() {
302 int answer = toType.hashCode();
303 if (fromType != null) {
304 answer *= 37 + fromType.hashCode();
305 }
306 return answer;
307 }
308
309 @Override
310 public String toString() {
311 return "[" + fromType + "=>" + toType + "]";
312 }
313 }
314 }