Coverage Report - de.smartics.properties.spi.config.support.ClassPathLoader
 
Classes in this File Line Coverage Branch Coverage Complexity
ClassPathLoader
0%
0/162
0%
0/52
2.409
 
 1  
 /*
 2  
  * Copyright 2012-2013 smartics, Kronseder & Reiner GmbH
 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 de.smartics.properties.spi.config.support;
 17  
 
 18  
 import java.io.BufferedInputStream;
 19  
 import java.io.IOException;
 20  
 import java.io.InputStream;
 21  
 import java.net.MalformedURLException;
 22  
 import java.net.URL;
 23  
 import java.net.URLClassLoader;
 24  
 import java.util.ArrayList;
 25  
 import java.util.Collection;
 26  
 import java.util.Collections;
 27  
 import java.util.HashMap;
 28  
 import java.util.List;
 29  
 import java.util.Map;
 30  
 import java.util.Map.Entry;
 31  
 import java.util.Properties;
 32  
 import java.util.Set;
 33  
 
 34  
 import javax.annotation.concurrent.NotThreadSafe;
 35  
 
 36  
 import org.apache.commons.io.IOUtils;
 37  
 import org.slf4j.Logger;
 38  
 import org.slf4j.LoggerFactory;
 39  
 
 40  
 import de.smartics.properties.api.config.app.BootProperties;
 41  
 import de.smartics.properties.api.config.domain.CompoundConfigurationException;
 42  
 import de.smartics.properties.api.config.domain.ConfigurationException;
 43  
 import de.smartics.properties.api.config.domain.ConfigurationPropertiesManagement;
 44  
 import de.smartics.properties.api.config.domain.ConfigurationRepositoryManagement;
 45  
 import de.smartics.properties.api.config.domain.Property;
 46  
 import de.smartics.properties.api.config.domain.PropertyLocation;
 47  
 import de.smartics.properties.api.config.domain.PropertyProvider;
 48  
 import de.smartics.properties.api.config.domain.key.ConfigurationKey;
 49  
 import de.smartics.properties.api.core.domain.PropertiesContext;
 50  
 import de.smartics.properties.api.core.domain.PropertyDescriptor;
 51  
 import de.smartics.properties.api.core.domain.PropertyDescriptorRegistry;
 52  
 import de.smartics.properties.api.core.security.PropertyValueSecurity;
 53  
 import de.smartics.properties.spi.config.definition.DefinitionKeyHelper;
 54  
 import de.smartics.properties.spi.core.classpath.PropertiesFilesLoader;
 55  
 import de.smartics.properties.spi.core.classpath.PropertySetClassesLoader;
 56  
 import de.smartics.properties.spi.core.metadata.PropertyMetaDataParser;
 57  
 import de.smartics.properties.spi.core.registry.InMemoryPropertyDescriptorRegistry;
 58  
 import de.smartics.properties.spi.core.util.ClassLoaderUtils;
 59  
 import de.smartics.util.lang.Arg;
 60  
 import de.smartics.util.lang.NullArgumentException;
 61  
 import de.smartics.util.lang.classpath.ClassPathContext;
 62  
 
 63  
 /**
 64  
  * Loads property descriptors and properties files found on the class path.
 65  
  *
 66  
  * @param <T> the concrete type of the returned configuration properties.
 67  
  */
 68  
 @NotThreadSafe
 69  
 public final class ClassPathLoader<T extends ConfigurationPropertiesManagement>
 70  
 { // NOPMD
 71  
   // ********************************* Fields *********************************
 72  
 
 73  
   // --- constants ------------------------------------------------------------
 74  
 
 75  
   /**
 76  
    * Reference to the logger for this class.
 77  
    */
 78  0
   private static final Logger LOG = LoggerFactory
 79  
       .getLogger(ClassPathLoader.class);
 80  
 
 81  
   // --- members --------------------------------------------------------------
 82  
 
 83  
   /**
 84  
    * The cache to create and cache new configuration properties instances.
 85  
    */
 86  
   private final FactoryCache<T> factoryCache;
 87  
 
 88  
   /**
 89  
    * The class root URLs to search for property descriptors and properties
 90  
    * files.
 91  
    */
 92  0
   private final Collection<URL> rootUrls = new ArrayList<URL>();
 93  
 
 94  
   /**
 95  
    * The root URLs to search for additional properties files. These URLs are
 96  
    * always considered for properties files.
 97  
    */
 98  0
   private final Collection<URL> additionalRootUrls = new ArrayList<URL>();
 99  
 
 100  
   /**
 101  
    * The additional properties added by other means than loading them from the
 102  
    * class path.
 103  
    */
 104  0
   private final Collection<PropertyProvider> rootPropertyProviders =
 105  
       new ArrayList<PropertyProvider>();
 106  
 
 107  
   /**
 108  
    * The flag indicates that configuration problems are not signaled by
 109  
    * exceptions.
 110  
    */
 111  
   private final boolean lenient;
 112  
 
 113  
   /**
 114  
    * The flag indicates that loading properties form the class path is to be
 115  
    * skipped.
 116  
    */
 117  
   private final boolean skipPropertyLoading;
 118  
 
 119  
   /**
 120  
    * The helper to decrypt secured property values.
 121  
    */
 122  
   private final PropertyValueSecurity decrypter;
 123  
 
 124  
   // ****************************** Initializer *******************************
 125  
 
 126  
   // ****************************** Constructors ******************************
 127  
 
 128  
   /**
 129  
    * Default constructor.
 130  
    *
 131  
    * @param factoryCache the cache to create and cache new configuration
 132  
    *          properties instances.
 133  
    * @param lenient the flag indicates that configuration problems are not
 134  
    *          signaled by exceptions.
 135  
    * @param skipPropertyLoading the flag indicates that loading properties from
 136  
    *          the class path is to be skipped.
 137  
    * @param decrypter the helper to decrypt secured property values.
 138  
    * @throws NullArgumentException if {@code factoryCache} or {@code decrypter}
 139  
    *           is <code>null</code>.
 140  
    */
 141  
   public ClassPathLoader(final FactoryCache<T> factoryCache,
 142  
       final boolean lenient, final boolean skipPropertyLoading,
 143  
       final PropertyValueSecurity decrypter) throws NullArgumentException
 144  0
   {
 145  0
     this.factoryCache = Arg.checkNotNull("factoryCache", factoryCache);
 146  0
     this.lenient = lenient;
 147  0
     this.skipPropertyLoading = skipPropertyLoading;
 148  0
     this.decrypter = Arg.checkNotNull("decrypter", decrypter);
 149  0
   }
 150  
 
 151  
   // ****************************** Inner Classes *****************************
 152  
 
 153  
   // ********************************* Methods ********************************
 154  
 
 155  
   // --- init -----------------------------------------------------------------
 156  
 
 157  
   // --- get&set --------------------------------------------------------------
 158  
 
 159  
   /**
 160  
    * Adds the given URL to the set of class path root URLs.
 161  
    *
 162  
    * @param rootUrl the URL to add as a class path root URL.
 163  
    * @throws NullArgumentException if {@code rootUrl} is <code>null</code>.
 164  
    */
 165  
   public void addRootUrl(final URL rootUrl) throws NullArgumentException
 166  
   {
 167  0
     Arg.checkNotNull("rootUrl", rootUrl);
 168  
 
 169  0
     if (!rootUrls.contains(rootUrl))
 170  
     {
 171  0
       rootUrls.add(rootUrl);
 172  
     }
 173  0
   }
 174  
 
 175  
   /**
 176  
    * Adds the root URL of the given {@code exemplar} to the set of class path
 177  
    * root URLs.
 178  
    *
 179  
    * @param exemplar a sample class to derive the root URL from.
 180  
    * @throws NullArgumentException if {@code exemplar} is <code>null</code>.
 181  
    */
 182  
   public void addRootUrl(final Class<?> exemplar) throws NullArgumentException
 183  
   {
 184  0
     Arg.checkNotNull("exemplar", exemplar);
 185  0
     addRootUrl(Thread.currentThread().getContextClassLoader());
 186  0
   }
 187  
 
 188  
   /**
 189  
    * Adds the relevant root URL of the given {@code classLoader} to the set of
 190  
    * class path root URLs.
 191  
    * <p>
 192  
    * Only archives that contain the
 193  
    * <code>{@value de.smartics.properties.api.core.domain.PropertiesContext#META_INF_HOME}</code>
 194  
    * are relevant and therefore added.
 195  
    * </p>
 196  
    *
 197  
    * @param classLoader the class loader whose class roots are added.
 198  
    * @throws NullArgumentException if {@code exemplar} is <code>null</code>.
 199  
    */
 200  
   public void addRootUrl(final ClassLoader classLoader)
 201  
     throws NullArgumentException
 202  
   {
 203  0
     Arg.checkNotNull("classLoader", classLoader);
 204  
 
 205  
     try
 206  
     {
 207  0
       addResources(classLoader);
 208  
     }
 209  0
     catch (final IOException e)
 210  
     {
 211  0
       LOG.warn("Cannot determine class path roots for the given class loader.",
 212  
           e);
 213  0
     }
 214  0
   }
 215  
 
 216  
   private void addResources(final ClassLoader classLoader) throws IOException
 217  
   {
 218  0
     for (final URL url : Collections.list(UrlUtil.getResourceUrlsFromFolder(
 219  
         classLoader, PropertiesContext.META_INF_HOME)))
 220  
     {
 221  0
       addRootUrl(url);
 222  
     }
 223  0
   }
 224  
 
 225  
   // --- business -------------------------------------------------------------
 226  
 
 227  
   /**
 228  
    * Loads the configuration properties instance from information found on the
 229  
    * class path.
 230  
    *
 231  
    * @return the loaded configuration properties instance.
 232  
    * @throws CompoundConfigurationException if loading encountered problems.
 233  
    */
 234  
   public ConfigurationRepositoryManagement load()
 235  
     throws CompoundConfigurationException
 236  
   {
 237  0
     final Set<Class<?>> propertyDescriptorTypes = loadPropertyDescriptors();
 238  0
     final Map<Class<?>, List<PropertyDescriptor>> descriptors =
 239  
         calcDescriptors(propertyDescriptorTypes);
 240  0
     factoryCache.registerDescriptors(descriptors);
 241  
 
 242  0
     final MultiSourcePropertiesManager propertiesManager = loadProperties();
 243  
     try
 244  
     {
 245  0
       for (final MultiSourceProperties properties : propertiesManager
 246  
           .getProperties())
 247  
       {
 248  0
         final List<ConfigurationException> exceptions =
 249  
             properties.getExceptions();
 250  0
         if (!exceptions.isEmpty())
 251  
         {
 252  0
           throw new CompoundConfigurationException(
 253  
               properties.getConfigurationKey(), exceptions);
 254  
         }
 255  
 
 256  0
         addProperties(descriptors, properties);
 257  0
       }
 258  
     }
 259  
     finally
 260  
     {
 261  0
       propertiesManager.release();
 262  0
     }
 263  
 
 264  0
     return factoryCache.getCache();
 265  
   }
 266  
 
 267  
   private Set<Class<?>> loadPropertyDescriptors()
 268  
   {
 269  0
     final PropertySetClassesLoader loader = new PropertySetClassesLoader();
 270  0
     final Set<Class<?>> propertyDescriptorTypes =
 271  
         loader.getPropertySetTypes(rootUrls);
 272  0
     return propertyDescriptorTypes;
 273  
   }
 274  
 
 275  
   private MultiSourcePropertiesManager loadProperties()
 276  
   {
 277  0
     final MultiSourcePropertiesManager allPropertiesManager =
 278  
         new MultiSourcePropertiesManager(lenient, rootPropertyProviders);
 279  
 
 280  0
     loadProperties(allPropertiesManager);
 281  
 
 282  0
     if (skipPropertyLoading)
 283  
     {
 284  0
       allPropertiesManager.create();
 285  
     }
 286  
 
 287  0
     return allPropertiesManager;
 288  
   }
 289  
 
 290  
   private void loadProperties(
 291  
       final MultiSourcePropertiesManager allPropertiesManager)
 292  
   {
 293  0
     final Collection<URL> propertiesUrls = createPropertiesUrls();
 294  0
     if (!propertiesUrls.isEmpty())
 295  
     {
 296  0
       final PropertiesFilesLoader loader = new PropertiesFilesLoader();
 297  0
       LOG.debug("Loading properties/Root location URLs: {}", propertiesUrls);
 298  0
       final Set<String> propertiesFiles =
 299  
           loader.getPropertiesFiles(propertiesUrls);
 300  
 
 301  0
       final ClassLoader classLoader =
 302  
           new URLClassLoader(propertiesUrls.toArray(new URL[propertiesUrls
 303  
               .size()]), Thread.currentThread().getContextClassLoader());
 304  0
       for (final String propertiesFile : propertiesFiles)
 305  
       {
 306  0
         if (propertiesFile.contains("META-INF"))
 307  
         {
 308  0
           continue;
 309  
         }
 310  
 
 311  0
         final ClassPathContext context =
 312  
             ClassLoaderUtils
 313  
                 .createClassPathContext(classLoader, propertiesFile);
 314  
 
 315  0
         if (!isDefintionsArchive(context))
 316  
         {
 317  0
           continue;
 318  
         }
 319  
 
 320  0
         final DefinitionKeyHelper helper =
 321  
             allPropertiesManager.getDefinition(context);
 322  0
         if (helper != null)
 323  
         {
 324  0
           addProperties(allPropertiesManager, classLoader, propertiesFile,
 325  
               helper);
 326  
         }
 327  
         else
 328  
         {
 329  0
           LOG.warn("Skipping '" + propertiesFile + "' since no '"
 330  
                    + PropertiesContext.META_INF_HOME + "' provided.");
 331  
         }
 332  0
       }
 333  
     }
 334  0
   }
 335  
 
 336  
   private static boolean isDefintionsArchive(final ClassPathContext context)
 337  
   {
 338  0
     final URL url = context.getResource(PropertiesContext.DEFINITION_FILE);
 339  0
     return url != null;
 340  
   }
 341  
 
 342  
   private Collection<URL> createPropertiesUrls()
 343  
   {
 344  0
     if (skipPropertyLoading)
 345  
     {
 346  0
       return this.additionalRootUrls;
 347  
     }
 348  
     else
 349  
     {
 350  0
       final Collection<URL> urls =
 351  
           new ArrayList<URL>(additionalRootUrls.size() + rootUrls.size());
 352  0
       urls.addAll(additionalRootUrls);
 353  0
       urls.addAll(rootUrls);
 354  0
       return urls;
 355  
     }
 356  
   }
 357  
 
 358  
   private void addProperties(
 359  
       final MultiSourcePropertiesManager allPropertiesManager,
 360  
       final ClassLoader classLoader, final String propertiesFile,
 361  
       final DefinitionKeyHelper helper) throws IllegalArgumentException
 362  
   {
 363  0
     final ConfigurationKey<?> key = helper.parse(propertiesFile);
 364  0
     final MultiSourceProperties allProperties =
 365  
         allPropertiesManager.create(key);
 366  
 
 367  0
     final PropertyLocation location =
 368  
         new PropertyLocationHelper().createPropertyLocation(classLoader,
 369  
             propertiesFile);
 370  0
     final Properties properties = loadProperties(classLoader, propertiesFile);
 371  0
     allProperties.add(location, properties);
 372  0
   }
 373  
 
 374  
   private Properties loadProperties(final ClassLoader classLoader,
 375  
       final String propertiesFile)
 376  
   {
 377  
     // TODO: There is a similar method in PropertiesHelper: Refactor
 378  0
     final Properties properties = new Properties();
 379  0
     InputStream in = classLoader.getResourceAsStream(propertiesFile);
 380  
     try
 381  
     {
 382  0
       if (in != null)
 383  
       {
 384  0
         in = new BufferedInputStream(in);
 385  0
         properties.load(in);
 386  
       }
 387  
       else
 388  
       {
 389  0
         LOG.warn("Cannot find properties '" + propertiesFile
 390  
                  + "' in class path.");
 391  
       }
 392  
     }
 393  0
     catch (final IOException e)
 394  
     {
 395  0
       LOG.warn("Cannot load properties from '" + propertiesFile + "'.");
 396  
     }
 397  
     finally
 398  
     {
 399  0
       IOUtils.closeQuietly(in);
 400  0
     }
 401  
 
 402  0
     return properties;
 403  
   }
 404  
 
 405  
   private Map<Class<?>, List<PropertyDescriptor>> calcDescriptors(
 406  
       final Set<Class<?>> propertyDescriptorTypes)
 407  
   {
 408  0
     final Map<Class<?>, List<PropertyDescriptor>> map =
 409  
         new HashMap<Class<?>, List<PropertyDescriptor>>();
 410  
 
 411  0
     for (final Class<?> type : propertyDescriptorTypes)
 412  
     {
 413  0
       final PropertiesContext context = factoryCache.getContext(type);
 414  0
       if (context == null)
 415  
       {
 416  0
         LOG.debug("Cannot find context for type '" + type.getName()
 417  
                   + "'. Skipping.");
 418  0
         continue;
 419  
       }
 420  0
       final PropertyMetaDataParser propertyDescriptorParser =
 421  
           PropertyMetaDataParser.create(context);
 422  0
       final List<PropertyDescriptor> descriptors =
 423  
           propertyDescriptorParser.readDescriptors(type);
 424  0
       map.put(type, descriptors);
 425  0
     }
 426  
 
 427  0
     return map;
 428  
   }
 429  
 
 430  
   private void addProperties(
 431  
       final Map<Class<?>, List<PropertyDescriptor>> descriptorMap,
 432  
       final MultiSourceProperties compositeProperties)
 433  
   {
 434  0
     final Properties properties = new Properties();
 435  0
     for (final Entry<Class<?>, List<PropertyDescriptor>> entry : descriptorMap
 436  
         .entrySet())
 437  
     {
 438  0
       final List<PropertyDescriptor> descriptors = entry.getValue();
 439  
 
 440  0
       for (final PropertyDescriptor descriptor : descriptors)
 441  
       {
 442  0
         final String propertyKey = descriptor.getKey().toString();
 443  0
         final Property property =
 444  
             compositeProperties.getValue(propertyKey, true);
 445  0
         if (property != null && property.getValue() != null)
 446  
         {
 447  0
           properties.put(propertyKey, property);
 448  
         }
 449  0
       }
 450  0
     }
 451  0
     final ConfigurationKey<?> key = compositeProperties.getConfigurationKey();
 452  0
     final ConfigurationPropertiesManagement configuration =
 453  
         factoryCache.ensureManagement(key);
 454  0
     final PropertyProvider provider =
 455  
         new PropertiesPropertyProvider(key, new PropertyLocation(
 456  
             "classpath-various"), properties);
 457  0
     configuration.addDefinitions(provider);
 458  0
   }
 459  
 
 460  
   /**
 461  
    * Adds all class path root URLs provided by the context class loader of the
 462  
    * current thread.
 463  
    */
 464  
   public void addDefaultRootUrls()
 465  
   {
 466  0
     addBootRootUrls();
 467  0
     addRootUrl(Thread.currentThread().getContextClassLoader());
 468  0
   }
 469  
 
 470  
   private void addBootRootUrls()
 471  
   {
 472  0
     final PropertyDescriptorRegistry registry =
 473  
         new InMemoryPropertyDescriptorRegistry();
 474  0
     final BootConfigurationProperties initBootConfiguration =
 475  
         new BootConfigurationProperties(registry, decrypter);
 476  0
     final BootLoader bootLoader =
 477  
         new BootLoader(initBootConfiguration, Thread.currentThread()
 478  
             .getContextClassLoader());
 479  0
     final ConfigurationPropertiesManagement bootConfiguration =
 480  
         bootLoader.loadAndValidate();
 481  0
     final BootProperties confProperties =
 482  
         bootConfiguration.getProperties(BootProperties.class);
 483  0
     final List<URL> urls = confProperties.additionalPropertiesLocations();
 484  0
     if (urls != null)
 485  
     {
 486  0
       for (final URL url : urls)
 487  
       {
 488  0
         final URL normalized = normalize(url);
 489  0
         addAdditionalRootUrl(normalized);
 490  0
       }
 491  
     }
 492  0
   }
 493  
 
 494  
   private void addAdditionalRootUrl(final URL rootUrl)
 495  
   {
 496  0
     Arg.checkNotNull("rootUrl", rootUrl);
 497  
 
 498  0
     if (!additionalRootUrls.contains(rootUrl))
 499  
     {
 500  0
       additionalRootUrls.add(rootUrl);
 501  
     }
 502  0
   }
 503  
 
 504  
   private static URL normalize(final URL url)
 505  
   {
 506  0
     final String urlString = url.toExternalForm();
 507  0
     if (urlString.indexOf(urlString.length() - 1) != '/')
 508  
     {
 509  
       try
 510  
       {
 511  0
         return new URL(urlString + '/');
 512  
       }
 513  0
       catch (final MalformedURLException e)
 514  
       {
 515  0
         LOG.warn("Cannot append '/' to '" + urlString
 516  
                  + "' for normalization. Using unnormalized URL instead.", e);
 517  
       }
 518  
     }
 519  0
     return url;
 520  
   }
 521  
 
 522  
   /**
 523  
    * Adds the given root URLs to the collection of class path roots managed by
 524  
    * this class loader. The root locations are used for searching property
 525  
    * declarations and property definitions.
 526  
    *
 527  
    * @param rootLocations the additional root locations to search for property
 528  
    *          declarations and definitions.
 529  
    */
 530  
   public void addRootUrls(final List<URL> rootLocations)
 531  
   {
 532  0
     this.rootUrls.addAll(rootLocations);
 533  0
   }
 534  
 
 535  
   /**
 536  
    * Adds the given property providers as additional property definitions.
 537  
    * Values provided by these instances are taken into account similar to any
 538  
    * property definitions found on the class path.
 539  
    * <p>
 540  
    * Property values provided by these providers take precedence over any
 541  
    * property values found on the class path.
 542  
    * </p>
 543  
    *
 544  
    * @param rootPropertyProviders the additional property definitions to add.
 545  
    */
 546  
   public void addRootProperties(
 547  
       final List<PropertyProvider> rootPropertyProviders)
 548  
   {
 549  0
     this.rootPropertyProviders.addAll(rootPropertyProviders);
 550  0
   }
 551  
 
 552  
   // --- object basics --------------------------------------------------------
 553  
 
 554  
   /**
 555  
    * Returns the string representation of the object.
 556  
    *
 557  
    * @return the string representation of the object.
 558  
    */
 559  
   @Override
 560  
   public String toString()
 561  
   {
 562  0
     final StringBuilder buffer = new StringBuilder();
 563  
 
 564  0
     final char returnChar = '\n';
 565  
 
 566  0
     buffer.append("== Root URLs:");
 567  0
     for (final URL url : rootUrls)
 568  
     {
 569  0
       buffer.append(returnChar).append("  ").append(url.toExternalForm());
 570  
     }
 571  
 
 572  0
     buffer.append(returnChar).append("== Additional root URLs:");
 573  0
     for (final URL url : additionalRootUrls)
 574  
     {
 575  0
       buffer.append(returnChar).append("  ").append(url.toExternalForm());
 576  
     }
 577  
 
 578  0
     buffer.append(returnChar).append("\n== Root Property Providers:");
 579  0
     for (final PropertyProvider provider : rootPropertyProviders)
 580  
     {
 581  0
       buffer.append(returnChar).append("  ").append(provider);
 582  
     }
 583  
 
 584  0
     buffer.append(returnChar).append("== Factory Cache:\n");
 585  0
     buffer.append(factoryCache);
 586  
 
 587  0
     buffer.append(returnChar).append("== Properties:");
 588  0
     buffer.append(returnChar).append("  Lenient validation  : ")
 589  
         .append(lenient);
 590  0
     buffer.append(returnChar).append("  Skip propertyLoading: ")
 591  
         .append(skipPropertyLoading);
 592  
 
 593  0
     return buffer.toString();
 594  
   }
 595  
 }