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