/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.camel.converter.dozer; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.camel.CamelContext; import org.apache.camel.CamelContextAware; import org.apache.camel.TypeConverter; import org.apache.camel.component.dozer.DozerComponent; import org.apache.camel.spi.ClassResolver; import org.apache.camel.spi.TypeConverterRegistry; import org.apache.camel.support.ServiceSupport; import org.apache.camel.util.ObjectHelper; import org.apache.camel.util.ResourceHelper; import org.dozer.DozerBeanMapper; import org.dozer.Mapper; import org.dozer.classmap.ClassMap; import org.dozer.classmap.MappingFileData; import org.dozer.loader.api.BeanMappingBuilder; import org.dozer.loader.xml.MappingFileReader; import org.dozer.loader.xml.XMLParserFactory; import org.dozer.util.DozerClassLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.dozer.classmap.MappingDirection.ONE_WAY; /** * <code>DozerTypeConverterLoader</code> provides the mechanism for registering * a Dozer {@link Mapper} as {@link TypeConverter} for a {@link CamelContext}. * <p/> * While a mapper can be explicitly supplied as a parameter the * {@link CamelContext}'s registry will also be searched for {@link Mapper} * instances. A {@link DozerTypeConverter} is created to wrap each * {@link Mapper} instance and the mapper is queried for the types it converts. * The queried types are used to register the {@link TypeConverter} with the * context via its {@link TypeConverterRegistry}. */ public class DozerTypeConverterLoader extends ServiceSupport implements CamelContextAware { private final Logger log = LoggerFactory.getLogger(getClass()); private CamelContext camelContext; private transient DozerBeanMapperConfiguration configuration; private transient DozerBeanMapper mapper; /** * Creates a <code>DozerTypeConverter</code> performing no * {@link TypeConverter} registration. */ public DozerTypeConverterLoader() { } /** * Creates a <code>DozerTypeConverter</code> that will search the given * {@link CamelContext} for instances of {@link DozerBeanMapper}. Each * discovered instance will be wrapped as a {@link DozerTypeConverter} and * register as a {@link TypeConverter} with the context * * @param camelContext the context to register the * {@link DozerTypeConverter} in */ public DozerTypeConverterLoader(CamelContext camelContext) { this.camelContext = camelContext; try { camelContext.addService(this); } catch (Exception e) { throw ObjectHelper.wrapRuntimeCamelException(e); } } /** * Creates a <code>DozerTypeConverter</code> using the given * {@link DozerBeanMapperConfiguration} configuration. * * @param camelContext the context to register the * {@link DozerTypeConverter} in * * @param configuration dozer mapping bean configuration. */ public DozerTypeConverterLoader(CamelContext camelContext, DozerBeanMapperConfiguration configuration) { ClassLoader tccl = Thread.currentThread().getContextClassLoader(); try { ClassLoader appcl = camelContext.getApplicationContextClassLoader(); if (appcl != null) { Thread.currentThread().setContextClassLoader(appcl); } log.info("Using DozerBeanMapperConfiguration: {}", configuration); DozerBeanMapper mapper = createDozerBeanMapper(configuration); this.camelContext = camelContext; this.mapper = mapper; this.configuration = configuration; camelContext.addService(this); } catch (Exception e) { throw ObjectHelper.wrapRuntimeCamelException(e); } finally { Thread.currentThread().setContextClassLoader(tccl); } } /** * Creates a <code>DozerTypeConverter</code> that will wrap the the given * {@link DozerBeanMapper} as a {@link DozerTypeConverter} and register it * with the given context. It will also search the context for * * @param camelContext the context to register the * {@link DozerTypeConverter} in * @param mapper the DozerMapperBean to be wrapped as a type converter. */ @Deprecated public DozerTypeConverterLoader(CamelContext camelContext, DozerBeanMapper mapper) { this.camelContext = camelContext; this.mapper = mapper; try { camelContext.addService(this); } catch (Exception e) { throw ObjectHelper.wrapRuntimeCamelException(e); } } /** * Doses the actual querying and registration of {@link DozerTypeConverter}s * with the {@link CamelContext}. * * @param camelContext the context to register the * {@link DozerTypeConverter} in * @param mapper the DozerMapperBean to be wrapped as a type converter. */ public void init(CamelContext camelContext, DozerBeanMapper mapper) { this.camelContext = camelContext; if (mapper != null) { this.mapper = mapper; } ClassLoader tccl = Thread.currentThread().getContextClassLoader(); try { ClassLoader appcl = camelContext.getApplicationContextClassLoader(); if (appcl != null) { Thread.currentThread().setContextClassLoader(appcl); } Map<String, DozerBeanMapper> mappers = lookupDozerBeanMappers(); // only add if we do not already have it if (mapper != null && !mappers.containsValue(mapper)) { mappers.put("parameter", mapper); } // add any dozer bean mapper configurations Map<String, DozerBeanMapperConfiguration> configurations = lookupDozerBeanMapperConfigurations(); if (configurations != null && configuration != null) { // filter out existing configuration, as we do not want to use it twice String key = null; for (Map.Entry<String, DozerBeanMapperConfiguration> entry : configurations.entrySet()) { if (entry.getValue() == configuration) { key = entry.getKey(); break; } } if (key != null) { configurations.remove(key); } } if (configurations != null) { if (configurations.size() > 1) { log.warn("Loaded " + configurations.size() + " Dozer configurations from Camel registry." + " Dozer is most efficient when there is a single mapper instance. Consider amalgamating instances."); } for (Map.Entry<String, DozerBeanMapperConfiguration> entry : configurations.entrySet()) { String id = entry.getKey(); DozerBeanMapper beanMapper = createDozerBeanMapper(entry.getValue()); // only add if we do not already have it if (!mappers.containsValue(beanMapper)) { mappers.put(id, beanMapper); } } } if (mappers.size() > 1) { log.warn("Loaded " + mappers.size() + " Dozer mappers from Camel registry." + " Dozer is most efficient when there is a single mapper instance. Consider amalgamating instances."); } else if (mappers.size() == 0) { log.warn("No Dozer mappers found in Camel registry. You should add Dozer mappers as beans to the registry of the type: " + DozerBeanMapper.class.getName()); } TypeConverterRegistry registry = camelContext.getTypeConverterRegistry(); for (Map.Entry<String, DozerBeanMapper> entry : mappers.entrySet()) { String mapperId = entry.getKey(); DozerBeanMapper dozer = entry.getValue(); List<ClassMap> all = loadMappings(camelContext, mapperId, dozer); registerClassMaps(registry, mapperId, dozer, all); } } finally { Thread.currentThread().setContextClassLoader(tccl); } } /** * Creates a {@link DozerBeanMapper} from the given configuration. * * @param configuration the dozer bean mapper configuration. * @return the created mapper */ public static DozerBeanMapper createDozerBeanMapper(DozerBeanMapperConfiguration configuration) { DozerBeanMapper mapper; if (configuration.getMappingFiles() != null) { mapper = DozerComponent.createDozerBeanMapper(configuration.getMappingFiles()); } else { mapper = DozerComponent.createDozerBeanMapper(Collections.<String>emptyList()); } if (configuration.getCustomConverters() != null) { mapper.setCustomConverters(configuration.getCustomConverters()); } if (configuration.getEventListeners() != null) { mapper.setEventListeners(configuration.getEventListeners()); } if (configuration.getCustomConvertersWithId() != null) { mapper.setCustomConvertersWithId(configuration.getCustomConvertersWithId()); } if (configuration.getCustomFieldMapper() != null) { mapper.setCustomFieldMapper(configuration.getCustomFieldMapper()); } return mapper; } /** * Lookup the dozer {@link DozerBeanMapper} to be used. */ protected Map<String, DozerBeanMapper> lookupDozerBeanMappers() { return new HashMap<String, DozerBeanMapper>(camelContext.getRegistry().findByTypeWithName(DozerBeanMapper.class)); } /** * Lookup the dozer {@link DozerBeanMapperConfiguration} to be used. */ protected Map<String, DozerBeanMapperConfiguration> lookupDozerBeanMapperConfigurations() { return new HashMap<String, DozerBeanMapperConfiguration>(camelContext.getRegistry().findByTypeWithName(DozerBeanMapperConfiguration.class)); } protected void registerClassMaps(TypeConverterRegistry registry, String dozerId, DozerBeanMapper dozer, List<ClassMap> all) { DozerTypeConverter converter = new DozerTypeConverter(dozer); for (ClassMap map : all) { addDozerTypeConverter(registry, converter, dozerId, map.getSrcClassToMap(), map.getDestClassToMap()); // if not one way then add the other way around also if (map.getType() != ONE_WAY) { addDozerTypeConverter(registry, converter, dozerId, map.getDestClassToMap(), map.getSrcClassToMap()); } } } protected void addDozerTypeConverter(TypeConverterRegistry registry, DozerTypeConverter converter, String dozerId, Class<?> to, Class<?> from) { if (log.isInfoEnabled()) { if (dozerId != null) { log.info("Added Dozer: {} as Camel type converter: {} -> {}", new Object[]{dozerId, from, to}); } else { log.info("Added Dozer as Camel type converter: {} -> {}", new Object[]{from, to}); } } registry.addTypeConverter(from, to, converter); } private List<ClassMap> loadMappings(CamelContext camelContext, String mapperId, DozerBeanMapper mapper) { List<ClassMap> answer = new ArrayList<ClassMap>(); // load the class map using the class resolver so we can load from classpath in OSGi MappingFileReader reader = new MappingFileReader(XMLParserFactory.getInstance()); List<String> mappingFiles = mapper.getMappingFiles(); if (mappingFiles == null) { return Collections.emptyList(); } for (String name : mappingFiles) { URL url = loadMappingFile(camelContext.getClassResolver(), name); if (url != null) { MappingFileData data = reader.read(url); answer.addAll(data.getClassMaps()); } } return answer; } /** * Registers Dozer <code>BeanMappingBuilder</code> in current mapper instance. * This method should be called instead of direct <code>mapper.addMapping()</code> invocation for Camel * being able to register given type conversion. * * @param beanMappingBuilder api-based mapping builder */ public void addMapping(BeanMappingBuilder beanMappingBuilder) { if (mapper == null) { log.warn("No mapper instance provided to " + this.getClass().getSimpleName() + ". Mapping has not been registered!"); return; } mapper.addMapping(beanMappingBuilder); MappingFileData mappingFileData = beanMappingBuilder.build(); TypeConverterRegistry registry = camelContext.getTypeConverterRegistry(); List<ClassMap> classMaps = new ArrayList<ClassMap>(); classMaps.addAll(mappingFileData.getClassMaps()); registerClassMaps(registry, null, mapper, classMaps); } public CamelContext getCamelContext() { return camelContext; } /** * Sets the {@link CamelContext} <b>and also</b> initializes this loader. * <p/> * The reason why {@link #init(org.apache.camel.CamelContext, org.dozer.DozerBeanMapper)} is also called * is because making using Dozer in Spring XML files easier, as no need to use the init-method attribute. * * @param camelContext the CamelContext */ public void setCamelContext(CamelContext camelContext) { if (this.camelContext == null) { this.camelContext = camelContext; try { camelContext.addService(this); } catch (Exception e) { throw ObjectHelper.wrapRuntimeCamelException(e); } } } public DozerBeanMapper getMapper() { return mapper; } public void setMapper(DozerBeanMapper mapper) { this.mapper = mapper; } protected static URL loadMappingFile(ClassResolver classResolver, String mappingFile) { URL url = null; try { url = ResourceHelper.resolveResourceAsUrl(classResolver, mappingFile); } catch (MalformedURLException e) { // ignore } if (url == null) { // using the classloader of DozerClassLoader as a fallback url = DozerClassLoader.class.getClassLoader().getResource(mappingFile); } return url; } @Override protected void doStart() throws Exception { init(camelContext, mapper); } @Override protected void doStop() throws Exception { // noop } }