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