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