Coverage Report - org.dozer.DozerBeanMapper
 
Classes in this File Line Coverage Branch Coverage Complexity
DozerBeanMapper
91%
103/113
83%
10/12
1.37
 
 1  
 /**
 2  
  * Copyright 2005-2013 Dozer Project
 3  
  *
 4  
  * Licensed under the Apache License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  *      http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 package org.dozer;
 17  
 
 18  
 import org.dozer.cache.CacheManager;
 19  
 import org.dozer.cache.DozerCacheManager;
 20  
 import org.dozer.cache.DozerCacheType;
 21  
 import org.dozer.classmap.ClassMappings;
 22  
 import org.dozer.classmap.Configuration;
 23  
 import org.dozer.classmap.MappingFileData;
 24  
 import org.dozer.config.GlobalSettings;
 25  
 import org.dozer.event.DozerEventManager;
 26  
 import org.dozer.factory.DestBeanCreator;
 27  
 import org.dozer.loader.CustomMappingsLoader;
 28  
 import org.dozer.loader.LoadMappingsResult;
 29  
 import org.dozer.loader.api.BeanMappingBuilder;
 30  
 import org.dozer.loader.xml.MappingFileReader;
 31  
 import org.dozer.loader.xml.MappingStreamReader;
 32  
 import org.dozer.loader.xml.XMLParserFactory;
 33  
 import org.dozer.metadata.DozerMappingMetadata;
 34  
 import org.dozer.metadata.MappingMetadata;
 35  
 import org.dozer.stats.GlobalStatistics;
 36  
 import org.dozer.stats.StatisticType;
 37  
 import org.dozer.stats.StatisticsInterceptor;
 38  
 import org.dozer.stats.StatisticsManager;
 39  
 import org.dozer.util.MappingValidator;
 40  
 import org.slf4j.Logger;
 41  
 import org.slf4j.LoggerFactory;
 42  
 
 43  
 import java.io.InputStream;
 44  
 import java.lang.reflect.Proxy;
 45  
 import java.net.URL;
 46  
 import java.util.*;
 47  
 import java.util.concurrent.CountDownLatch;
 48  
 import java.util.concurrent.atomic.AtomicBoolean;
 49  
 
 50  
 /**
 51  
  * Public Dozer Mapper implementation. This should be used/defined as a singleton within your application. This class
 52  
  * performs several one-time initializations and loads the custom xml mappings, so you will not want to create many
 53  
  * instances of it for performance reasons. Typically a system will only have one DozerBeanMapper instance per VM. If
 54  
  * you are using an IOC framework (i.e Spring), define the Mapper as singleton="true". If you are not using an IOC
 55  
  * framework, a DozerBeanMapperSingletonWrapper convenience class has been provided in the Dozer jar.
 56  
  * <p/>
 57  
  * It is technically possible to have multiple DozerBeanMapper instances initialized, but it will hinder internal
 58  
  * performance optimizations such as caching.
 59  
  *
 60  
  * @author tierney.matt
 61  
  * @author garsombke.franz
 62  
  * @author dmitry.buzdin
 63  
  * @author suwarnaratana.arm
 64  
  */
 65  
 public class DozerBeanMapper implements Mapper {
 66  
 
 67  1134
   private final Logger log = LoggerFactory.getLogger(DozerBeanMapper.class);
 68  
 
 69  1134
   private final StatisticsManager statsMgr = GlobalStatistics.getInstance().getStatsMgr();
 70  1134
   private final AtomicBoolean initializing = new AtomicBoolean(false);
 71  1134
   private final CountDownLatch ready = new CountDownLatch(1);
 72  
 
 73  
   /*
 74  
    * Accessible for custom injection
 75  
    */
 76  1134
   private final List<String> mappingFiles = new ArrayList<String>();
 77  1134
   private final List<CustomConverter> customConverters = new ArrayList<CustomConverter>();
 78  1134
   private final List<MappingFileData> builderMappings = new ArrayList<MappingFileData>();
 79  1134
   private final List<DozerEventListener> eventListeners = new ArrayList<DozerEventListener>();
 80  1134
   private final Map<String, CustomConverter> customConvertersWithId = new HashMap<String, CustomConverter>();
 81  
 
 82  
   private CustomFieldMapper customFieldMapper;
 83  
 
 84  
   /*
 85  
    * Not accessible for injection
 86  
    */
 87  
   private ClassMappings customMappings;
 88  
   private Configuration globalConfiguration;
 89  
   // There are no global caches. Caches are per bean mapper instance
 90  1134
   private final CacheManager cacheManager = new DozerCacheManager();
 91  
   private DozerEventManager eventManager;
 92  
 
 93  
   public DozerBeanMapper() {
 94  1105
     this(Collections.<String>emptyList());
 95  1105
   }
 96  
 
 97  1134
   public DozerBeanMapper(List<String> mappingFiles) {
 98  1134
     this.mappingFiles.addAll(mappingFiles);
 99  1134
     init();
 100  1134
   }
 101  
 
 102  
   /**
 103  
    * {@inheritDoc}
 104  
    */
 105  
   public void map(Object source, Object destination, String mapId) throws MappingException {
 106  22
     getMappingProcessor().map(source, destination, mapId);
 107  22
   }
 108  
 
 109  
   /**
 110  
    * {@inheritDoc}
 111  
    */
 112  
   public <T> T map(Object source, Class<T> destinationClass, String mapId) throws MappingException {
 113  73
     return getMappingProcessor().map(source, destinationClass, mapId);
 114  
   }
 115  
 
 116  
   /**
 117  
    * {@inheritDoc}
 118  
    */
 119  
   public <T> T map(Object source, Class<T> destinationClass) throws MappingException {
 120  790
     return getMappingProcessor().map(source, destinationClass);
 121  
   }
 122  
 
 123  
   /**
 124  
    * {@inheritDoc}
 125  
    */
 126  
   public void map(Object source, Object destination) throws MappingException {
 127  131178
     getMappingProcessor().map(source, destination);
 128  131177
   }
 129  
 
 130  
   /**
 131  
    * Returns list of provided mapping file URLs
 132  
    *
 133  
    * @return unmodifiable list of mapping files
 134  
    */
 135  
   public List<String> getMappingFiles() {
 136  1
     return Collections.unmodifiableList(mappingFiles);
 137  
   }
 138  
 
 139  
   /**
 140  
    * Sets list of URLs for custom XML mapping files, which are loaded when mapper gets initialized.
 141  
    * It is possible to load files from file system via file: prefix. If no prefix is given mapping files are
 142  
    * loaded from classpath and can be packaged along with the application.
 143  
    *
 144  
    * @param mappingFileUrls URLs referencing custom mapping files
 145  
    * @see java.net.URL
 146  
    */
 147  
   public void setMappingFiles(List<String> mappingFileUrls) {
 148  592
     checkIfInitialized();
 149  591
     this.mappingFiles.clear();
 150  591
     this.mappingFiles.addAll(mappingFileUrls);
 151  591
   }
 152  
 
 153  
   public void setFactories(Map<String, BeanFactory> factories) {
 154  1
     checkIfInitialized();
 155  0
     DestBeanCreator.setStoredFactories(factories);
 156  0
   }
 157  
 
 158  
   public void setCustomConverters(List<CustomConverter> customConverters) {
 159  3
     checkIfInitialized();
 160  2
     this.customConverters.clear();
 161  2
     this.customConverters.addAll(customConverters);
 162  2
   }
 163  
 
 164  
   public List<CustomConverter> getCustomConverters() {
 165  1
     return Collections.unmodifiableList(customConverters);
 166  
   }
 167  
 
 168  
   public Map<String, CustomConverter> getCustomConvertersWithId() {
 169  1
     return Collections.unmodifiableMap(customConvertersWithId);
 170  
   }
 171  
 
 172  
   private void init() {
 173  1134
     DozerInitializer.getInstance().init();
 174  
 
 175  1134
     log.info("Initializing a new instance of dozer bean mapper.");
 176  
 
 177  
     // initialize any bean mapper caches. These caches are only visible to the bean mapper instance and
 178  
     // are not shared across the VM.
 179  1134
     GlobalSettings globalSettings = GlobalSettings.getInstance();
 180  1134
     cacheManager.addCache(DozerCacheType.CONVERTER_BY_DEST_TYPE.name(), globalSettings.getConverterByDestTypeCacheMaxSize());
 181  1134
     cacheManager.addCache(DozerCacheType.SUPER_TYPE_CHECK.name(), globalSettings.getSuperTypesCacheMaxSize());
 182  
 
 183  
     // stats
 184  1134
     statsMgr.increment(StatisticType.MAPPER_INSTANCES_COUNT);
 185  1134
   }
 186  
 
 187  
   public void destroy() {
 188  1
     DozerInitializer.getInstance().destroy();
 189  1
   }
 190  
 
 191  
   protected Mapper getMappingProcessor() {
 192  132074
     initMappings();
 193  
 
 194  132059
     Mapper processor = new MappingProcessor(customMappings, globalConfiguration, cacheManager, statsMgr, customConverters,
 195  
             eventManager, getCustomFieldMapper(), customConvertersWithId);
 196  
 
 197  
     // If statistics are enabled, then Proxy the processor with a statistics interceptor
 198  132059
     if (statsMgr.isStatisticsEnabled()) {
 199  0
       processor = (Mapper) Proxy.newProxyInstance(processor.getClass().getClassLoader(),
 200  
               processor.getClass().getInterfaces(),
 201  
               new StatisticsInterceptor(processor, statsMgr));
 202  
     }
 203  
 
 204  132059
     return processor;
 205  
   }
 206  
 
 207  
   void loadCustomMappings() {
 208  698
     CustomMappingsLoader customMappingsLoader = new CustomMappingsLoader();
 209  698
     List<MappingFileData> xmlMappings = loadFromFiles(mappingFiles);
 210  690
     ArrayList<MappingFileData> allMappings = new ArrayList<MappingFileData>();
 211  690
     allMappings.addAll(xmlMappings);
 212  690
     allMappings.addAll(builderMappings);
 213  690
     LoadMappingsResult loadMappingsResult = customMappingsLoader.load(allMappings);
 214  683
     this.customMappings = loadMappingsResult.getCustomMappings();
 215  683
     this.globalConfiguration = loadMappingsResult.getGlobalConfiguration();
 216  683
   }
 217  
 
 218  
   private List<MappingFileData> loadFromFiles(List<String> mappingFiles) {
 219  698
     MappingFileReader mappingFileReader = new MappingFileReader(XMLParserFactory.getInstance());
 220  698
     List<MappingFileData> mappingFileDataList = new ArrayList<MappingFileData>();
 221  698
     if (mappingFiles != null && mappingFiles.size() > 0) {
 222  594
       log.info("Using the following xml files to load custom mappings for the bean mapper instance: {}", mappingFiles);
 223  594
       for (String mappingFileName : mappingFiles) {
 224  600
         log.info("Trying to find xml mapping file: {}", mappingFileName);
 225  600
         URL url = MappingValidator.validateURL(mappingFileName);
 226  600
         log.info("Using URL [" + url + "] to load custom xml mappings");
 227  600
         MappingFileData mappingFileData = mappingFileReader.read(url);
 228  592
         log.info("Successfully loaded custom xml mappings from URL: [{}]", url);
 229  
 
 230  592
         mappingFileDataList.add(mappingFileData);
 231  592
       }
 232  
     }
 233  690
     return mappingFileDataList;
 234  
   }
 235  
 
 236  
   /**
 237  
    * Add mapping XML from InputStream resources for mapping not stored in
 238  
    * files (e.g. from database.) The InputStream will be read immediately to
 239  
    * internally create MappingFileData objects so that the InputStreams may be
 240  
    * closed after the call to this method.
 241  
    *
 242  
          * @param xmlStream Dozer mapping XML InputStream
 243  
          */
 244  
         public void addMapping(InputStream xmlStream) {
 245  1
     checkIfInitialized();
 246  0
     MappingStreamReader fileReader = new MappingStreamReader(XMLParserFactory.getInstance());
 247  0
     MappingFileData mappingFileData = fileReader.read(xmlStream);
 248  0
     builderMappings.add(mappingFileData);
 249  0
   }
 250  
 
 251  
   /**
 252  
    * Adds API mapping to given mapper instance.
 253  
    *
 254  
    * @param mappingBuilder mappings to be added
 255  
    */
 256  
   public void addMapping(BeanMappingBuilder mappingBuilder) {
 257  28
     checkIfInitialized();
 258  27
     MappingFileData mappingFileData = mappingBuilder.build();
 259  27
     builderMappings.add(mappingFileData);
 260  27
   }
 261  
 
 262  
   public List<? extends DozerEventListener> getEventListeners() {
 263  2
     return Collections.unmodifiableList(eventListeners);
 264  
   }
 265  
 
 266  
   public void setEventListeners(List<? extends DozerEventListener> eventListeners) {
 267  2
     checkIfInitialized();
 268  1
     this.eventListeners.clear();
 269  1
     this.eventListeners.addAll(eventListeners);
 270  1
   }
 271  
 
 272  
   public CustomFieldMapper getCustomFieldMapper() {
 273  132059
     return customFieldMapper;
 274  
   }
 275  
 
 276  
   public void setCustomFieldMapper(CustomFieldMapper customFieldMapper) {
 277  3
     checkIfInitialized();
 278  2
     this.customFieldMapper = customFieldMapper;
 279  2
   }
 280  
 
 281  
   /**
 282  
    * The {@link org.dozer.metadata.MappingMetadata} interface can be used to query information about the current
 283  
    * mapping definitions. It provides read only access to all important classes and field
 284  
    * mapping properties. When first called, initializes all mappings if map() has not yet been called.
 285  
    *
 286  
    * @return An instance of {@line org.dozer.metadata.MappingMetadata} which serves starting point 
 287  
    * for querying mapping information. 
 288  
    */
 289  
   public MappingMetadata getMappingMetadata() {
 290  48
     initMappings();
 291  48
     return new DozerMappingMetadata(customMappings);
 292  
   }
 293  
 
 294  
   /**
 295  
    * Converters passed with this method could be further referenced in mappings via its unique id.
 296  
    * Converter instances passed that way are considered stateful and will not be initialized for each mapping.
 297  
    *
 298  
    * @param customConvertersWithId converter id to converter instance map
 299  
    */
 300  
   public void setCustomConvertersWithId(Map<String, CustomConverter> customConvertersWithId) {
 301  5
     checkIfInitialized();
 302  4
     this.customConvertersWithId.clear();
 303  4
     this.customConvertersWithId.putAll(customConvertersWithId);
 304  4
   }
 305  
 
 306  
   private void checkIfInitialized() {
 307  635
     if (ready.getCount() == 0) {
 308  8
       throw new MappingException("Dozer Bean Mapper is already initialized! Modify settings before calling map()");
 309  
     }
 310  627
   }
 311  
 
 312  
   private void initMappings() {
 313  132122
     if (initializing.compareAndSet(false, true)) {
 314  
       try {
 315  699
         loadCustomMappings();
 316  684
         eventManager = new DozerEventManager(eventListeners);
 317  15
       } catch (RuntimeException e) {
 318  
         // reset initialized state if error happens
 319  15
         initializing.set(false);
 320  15
         throw e;
 321  
       } finally {
 322  699
         ready.countDown();
 323  684
       }
 324  
     }
 325  
 
 326  
     try {
 327  132107
       ready.await();
 328  0
     } catch (InterruptedException e) {
 329  0
       log.error("Thread interrupted: ", e);
 330  
       // Restore the interrupted status:
 331  0
       Thread.currentThread().interrupt();
 332  132107
     }
 333  132107
   }
 334  
 
 335  
 }