View Javadoc

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.URL;
22  import java.net.URLClassLoader;
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.Enumeration;
26  import java.util.List;
27  import java.util.Properties;
28  import java.util.Set;
29  
30  import javax.annotation.concurrent.NotThreadSafe;
31  
32  import org.apache.commons.io.IOUtils;
33  import org.slf4j.Logger;
34  import org.slf4j.LoggerFactory;
35  
36  import de.smartics.properties.api.config.app.BootProperties;
37  import de.smartics.properties.api.config.domain.CompoundConfigurationException;
38  import de.smartics.properties.api.config.domain.ConfigurationException;
39  import de.smartics.properties.api.config.domain.ConfigurationPropertiesManagement;
40  import de.smartics.properties.api.config.domain.ConfigurationValidationException;
41  import de.smartics.properties.api.config.domain.Property;
42  import de.smartics.properties.api.config.domain.PropertyLocation;
43  import de.smartics.properties.api.config.domain.PropertyProvider;
44  import de.smartics.properties.api.core.domain.PropertiesContext;
45  import de.smartics.properties.api.core.domain.PropertyDescriptor;
46  import de.smartics.properties.spi.core.classpath.PropertiesFilesLoader;
47  import de.smartics.properties.spi.core.metadata.PropertyMetaDataParser;
48  import de.smartics.util.lang.Arg;
49  import de.smartics.util.lang.NullArgumentException;
50  
51  /**
52   * Loads boot properties that influence the loading of properties.
53   */
54  @NotThreadSafe
55  public final class BootLoader
56  {
57    // ********************************* Fields *********************************
58  
59    // --- constants ------------------------------------------------------------
60  
61    /**
62     * Reference to the logger for this class.
63     */
64    private static final Logger LOG = LoggerFactory.getLogger(BootLoader.class);
65  
66    // --- members --------------------------------------------------------------
67  
68    /**
69     * The configuration to add properties to.
70     */
71    private final ConfigurationPropertiesManagement configuration;
72  
73    /**
74     * The class root URLs to search for property descriptors and properties
75     * files.
76     */
77    private final Collection<URL> rootUrls;
78  
79    // ****************************** Initializer *******************************
80  
81    // ****************************** Constructors ******************************
82  
83    /**
84     * Convenience constructor to initialize class path root URLs.
85     *
86     * @param configuration the configuration to add properties to.
87     * @param classLoader the class loader whose class roots are added.
88     * @throws NullArgumentException if {@code configuration} or {@code rootUrls}
89     *           is <code>null</code>.
90     */
91    public BootLoader(final ConfigurationPropertiesManagement configuration,
92        final ClassLoader classLoader) throws NullArgumentException
93    {
94      this.configuration = Arg.checkNotNull("configuration", configuration);
95      this.rootUrls = readRootUrls(Arg.checkNotNull("classLoader", classLoader));
96    }
97  
98    // ****************************** Inner Classes *****************************
99  
100   // ********************************* Methods ********************************
101 
102   // --- init -----------------------------------------------------------------
103 
104   private static List<URL> readRootUrls(final ClassLoader classLoader)
105     throws NullArgumentException
106   {
107     final List<URL> list = new ArrayList<URL>();
108     try
109     {
110       final Enumeration<URL> rootUrls =
111           UrlUtil.getResourceUrls(classLoader, "");
112 
113       while (rootUrls.hasMoreElements())
114       {
115         final URL rootUrl = rootUrls.nextElement();
116         list.add(rootUrl);
117       }
118       extendClasspath(classLoader, list);
119     }
120     catch (final IOException e)
121     {
122       LOG.warn("Cannot determine class path roots for the given class loader.",
123           e);
124     }
125 
126     return list;
127   }
128 
129   /**
130    * Extends the classpath to contain more / all jars found in this classloader.
131    *
132    * @param classLoader the classloader in which more resources shall be
133    *          searched for.
134    * @param list the list to which the additional URLs are appended.
135    * @throws IOException when the resources can not be read.
136    */
137   private static void extendClasspath(final ClassLoader classLoader,
138       final List<URL> list) throws IOException
139   {
140     final Enumeration<URL> metaInfRootUrls =
141         UrlUtil.getResourceUrls(classLoader, "/META-INF");
142     while (metaInfRootUrls.hasMoreElements())
143     {
144       final URL rootUrl = metaInfRootUrls.nextElement();
145       if (!list.contains(rootUrl))
146       {
147         list.add(rootUrl);
148       }
149     }
150   }
151 
152   // --- get&set --------------------------------------------------------------
153 
154   // --- business -------------------------------------------------------------
155 
156   /**
157    * Loads the configuration properties instance from information found on the
158    * class path.
159    *
160    * @return the loaded configuration properties instance.
161    * @throws CompoundConfigurationException if loading encountered problems.
162    */
163   public ConfigurationPropertiesManagement load()
164     throws CompoundConfigurationException
165   {
166     final MultiSourceProperties properties = loadProperties();
167 
168     final List<ConfigurationException> exceptions = properties.getExceptions();
169     if (!exceptions.isEmpty())
170     {
171       throw new CompoundConfigurationException(configuration.getKey(),
172           exceptions);
173     }
174 
175     addProperties(properties);
176 
177     return configuration;
178   }
179 
180   /**
181    * Loads the configuration properties instance from information found on the
182    * class path.
183    *
184    * @return the loaded configuration properties instance.
185    * @throws ConfigurationValidationException if the validation of the
186    *           configuration failed.
187    */
188   public ConfigurationPropertiesManagement loadAndValidate()
189     throws ConfigurationValidationException
190   {
191     load();
192     configuration.validate(true);
193 
194     return configuration;
195   }
196 
197   private MultiSourceProperties loadProperties()
198   {
199     final MultiSourceProperties allProperties =
200         new MultiSourceProperties(configuration.getKey(),
201             new ArrayList<ConfigurationException>());
202 
203     final PropertiesFilesLoader loader = new PropertiesFilesLoader();
204     final Set<String> propertiesFiles = loader.getBootPropertiesFiles(rootUrls);
205 
206     final ClassLoader classLoader =
207         new URLClassLoader(rootUrls.toArray(new URL[rootUrls.size()]), Thread
208             .currentThread().getContextClassLoader());
209     for (final String propertiesFile : propertiesFiles)
210     {
211       final Properties properties = loadProperties(classLoader, propertiesFile);
212       final PropertyLocation location =
213           new PropertyLocationHelper().createPropertyLocation(classLoader,
214               propertiesFile);
215       allProperties.add(location, properties);
216     }
217 
218     return allProperties;
219   }
220 
221   private Properties loadProperties(final ClassLoader classLoader,
222       final String propertiesFile)
223   {
224     // TODO: There is a similar method in PropertiesUtils: Refactor
225     final Properties properties = new Properties();
226     InputStream in = classLoader.getResourceAsStream(propertiesFile);
227     try
228     {
229       if (in != null)
230       {
231         in = new BufferedInputStream(in);
232         properties.load(in);
233       }
234       else
235       {
236         LOG.warn("Cannot find properties '" + propertiesFile
237                  + "' in class path.");
238       }
239     }
240     catch (final IOException e)
241     {
242       LOG.warn("Cannot load properties from '" + propertiesFile + "'.");
243     }
244     finally
245     {
246       IOUtils.closeQuietly(in);
247     }
248 
249     return properties;
250   }
251 
252   private void addProperties(final MultiSourceProperties compositeProperties)
253   {
254     final Properties properties = new Properties();
255     final Class<?> type = BootProperties.class;
256 
257     final PropertiesContext context = configuration.getContext(type);
258     final PropertyMetaDataParser propertyDescriptorParser =
259         PropertyMetaDataParser.create(context);
260 
261     final List<PropertyDescriptor> descriptors =
262         propertyDescriptorParser.readDescriptors(type);
263 
264     for (final PropertyDescriptor descriptor : descriptors)
265     {
266       final String propertyKey = descriptor.getKey().toString();
267       final Property property = compositeProperties.getValue(propertyKey);
268       if (property != null && property.getValue() != null)
269       {
270         properties.put(propertyKey, property);
271       }
272       else
273       {
274         properties.remove(propertyKey);
275       }
276     }
277 
278     configuration.addDescriptors(type);
279     final PropertyProvider provider =
280         new PropertiesPropertyProvider(configuration.getKey(),
281             new PropertyLocation("boot-various"), properties);
282     configuration.addDefinitions(provider);
283   }
284 
285   // --- object basics --------------------------------------------------------
286 
287 }