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