1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.spi.config.definition.DefinitionKeyHelper;
53 import de.smartics.properties.spi.core.classpath.PropertiesFilesLoader;
54 import de.smartics.properties.spi.core.classpath.PropertySetClassesLoader;
55 import de.smartics.properties.spi.core.metadata.PropertyMetaDataParser;
56 import de.smartics.properties.spi.core.util.ClassLoaderUtils;
57 import de.smartics.util.lang.Arguments;
58 import de.smartics.util.lang.ClassPathContext;
59 import de.smartics.util.lang.NullArgumentException;
60
61
62
63
64
65
66 @NotThreadSafe
67 public final class ClassPathLoader<T extends ConfigurationPropertiesManagement>
68 {
69
70
71
72
73
74
75
76 private static final Logger LOG = LoggerFactory
77 .getLogger(ClassPathLoader.class);
78
79
80
81
82
83
84 private final FactoryCache<T> factoryCache;
85
86
87
88
89
90 private final Collection<URL> rootUrls = new ArrayList<URL>();
91
92
93
94
95
96 private final Collection<PropertyProvider> rootPropertyProviders =
97 new ArrayList<PropertyProvider>();
98
99
100
101
102
103 private final boolean lenient;
104
105
106
107
108
109 private final boolean skipPropertyLoading;
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127 public ClassPathLoader(final FactoryCache<T> factoryCache,
128 final boolean lenient, final boolean skipPropertyLoading)
129 throws NullArgumentException
130 {
131 Arguments.checkNotNull("factoryCache", factoryCache);
132
133 this.factoryCache = factoryCache;
134 this.lenient = lenient;
135 this.skipPropertyLoading = skipPropertyLoading;
136 }
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152 public void addRootUrl(final URL rootUrl) throws NullArgumentException
153 {
154 Arguments.checkNotNull("rootUrl", rootUrl);
155
156 if (!rootUrls.contains(rootUrl))
157 {
158 rootUrls.add(rootUrl);
159 }
160 }
161
162
163
164
165
166
167
168
169 public void addRootUrl(final Class<?> exemplar) throws NullArgumentException
170 {
171 Arguments.checkNotNull("exemplar", exemplar);
172 addRootUrl(Thread.currentThread().getContextClassLoader());
173 }
174
175
176
177
178
179
180
181
182
183
184
185
186
187 public void addRootUrl(final ClassLoader classLoader)
188 throws NullArgumentException
189 {
190 Arguments.checkNotNull("classLoader", classLoader);
191
192 try
193 {
194 addResources(classLoader);
195 }
196 catch (final IOException e)
197 {
198 LOG.warn("Cannot determine class path roots for the given class loader.",
199 e);
200 }
201 }
202
203 private void addResources(final ClassLoader classLoader) throws IOException
204 {
205 for (final URL url : Collections.list(classLoader
206 .getResources(PropertiesContext.META_INF_HOME)))
207 {
208 final URL rootUrl = truncateUrl(url);
209 addRootUrl(rootUrl);
210 }
211 }
212
213 private static URL truncateUrl(final URL url)
214 {
215 final String urlString = url.toExternalForm();
216 final boolean isJar = "jar".equals(url.getProtocol());
217 final int last;
218 if (isJar)
219 {
220 last = urlString.indexOf('!') + 2;
221 }
222 else
223 {
224 last = urlString.length() - PropertiesContext.META_INF_HOME.length() - 1;
225 }
226
227 final String urlStringTruncated = urlString.substring(0, last);
228 URL urlTruncated;
229 try
230 {
231 urlTruncated = new URL(urlStringTruncated);
232 return urlTruncated;
233 }
234 catch (final MalformedURLException e)
235 {
236 LOG.warn("Cannot use URL '{}' in its truncated form '{}'.", url,
237 urlStringTruncated);
238 return url;
239 }
240 }
241
242
243
244
245
246
247
248
249
250
251 public ConfigurationRepositoryManagement load()
252 throws CompoundConfigurationException
253 {
254 final Set<Class<?>> propertyDescriptorTypes = loadPropertyDescriptors();
255 final Map<Class<?>, List<PropertyDescriptor>> descriptors =
256 calcDescriptors(propertyDescriptorTypes);
257 factoryCache.registerDescriptors(descriptors);
258
259 final MultiSourcePropertiesManager propertiesManager = loadProperties();
260
261 for (final MultiSourceProperties properties : propertiesManager
262 .getProperties())
263 {
264 final List<ConfigurationException> exceptions =
265 properties.getExceptions();
266 if (!exceptions.isEmpty())
267 {
268 throw new CompoundConfigurationException(
269 properties.getConfigurationKey(), exceptions);
270 }
271
272 addProperties(descriptors, properties);
273 }
274
275 return factoryCache.getCache();
276 }
277
278 private Set<Class<?>> loadPropertyDescriptors()
279 {
280 final PropertySetClassesLoader loader = new PropertySetClassesLoader();
281 final Set<Class<?>> propertyDescriptorTypes =
282 loader.getPropertySetTypes(rootUrls);
283 return propertyDescriptorTypes;
284 }
285
286 private MultiSourcePropertiesManager loadProperties()
287 {
288 final MultiSourcePropertiesManager allPropertiesManager =
289 new MultiSourcePropertiesManager(lenient, rootPropertyProviders);
290
291 if (!skipPropertyLoading)
292 {
293 final PropertiesFilesLoader loader = new PropertiesFilesLoader();
294 LOG.debug("Loading properties/Root location URLs: {}", rootUrls);
295 final Set<String> propertiesFiles = loader.getPropertiesFiles(rootUrls);
296
297 final ClassLoader classLoader =
298 new URLClassLoader(rootUrls.toArray(new URL[rootUrls.size()]), Thread
299 .currentThread().getContextClassLoader());
300 for (final String propertiesFile : propertiesFiles)
301 {
302 if (propertiesFile.contains("META-INF"))
303 {
304 continue;
305 }
306
307 final ClassPathContext context =
308 ClassLoaderUtils
309 .createClassPathContext(classLoader, propertiesFile);
310 final DefinitionKeyHelper helper =
311 allPropertiesManager.getDefinition(context);
312 if (helper != null)
313 {
314 addProperties(allPropertiesManager, classLoader, propertiesFile,
315 helper);
316 }
317 else
318 {
319 LOG.warn("Skipping '" + propertiesFile + "' since no '"
320 + PropertiesContext.META_INF_HOME + "' provided.");
321 }
322 }
323 }
324 else
325 {
326 allPropertiesManager.create();
327 }
328
329 return allPropertiesManager;
330 }
331
332 private void addProperties(
333 final MultiSourcePropertiesManager allPropertiesManager,
334 final ClassLoader classLoader, final String propertiesFile,
335 final DefinitionKeyHelper helper) throws IllegalArgumentException
336 {
337 final ConfigurationKey key = helper.parse(propertiesFile);
338 final MultiSourceProperties allProperties =
339 allPropertiesManager.create(key);
340
341 final PropertyLocation location =
342 new PropertyLocationHelper().createPropertyLocation(classLoader,
343 propertiesFile);
344 final Properties properties = loadProperties(classLoader, propertiesFile);
345 allProperties.add(location, properties);
346 }
347
348 private Properties loadProperties(final ClassLoader classLoader,
349 final String propertiesFile)
350 {
351
352 final Properties properties = new Properties();
353 InputStream in = classLoader.getResourceAsStream(propertiesFile);
354 try
355 {
356 if (in != null)
357 {
358 in = new BufferedInputStream(in);
359 properties.load(in);
360 }
361 else
362 {
363 LOG.warn("Cannot find properties '" + propertiesFile
364 + "' in class path.");
365 }
366 }
367 catch (final IOException e)
368 {
369 LOG.warn("Cannot load properties from '" + propertiesFile + "'.");
370 }
371 finally
372 {
373 IOUtils.closeQuietly(in);
374 }
375
376 return properties;
377 }
378
379 private Map<Class<?>, List<PropertyDescriptor>> calcDescriptors(
380 final Set<Class<?>> propertyDescriptorTypes)
381 {
382 final Map<Class<?>, List<PropertyDescriptor>> map =
383 new HashMap<Class<?>, List<PropertyDescriptor>>();
384
385 for (final Class<?> type : propertyDescriptorTypes)
386 {
387 final PropertiesContext context = factoryCache.getContext(type);
388 if (context == null)
389 {
390 LOG.debug("Cannot find context for type '" + type.getName()
391 + "'. Skipping.");
392 continue;
393 }
394 final PropertyMetaDataParser propertyDescriptorParser =
395 PropertyMetaDataParser.create(context);
396 final List<PropertyDescriptor> descriptors =
397 propertyDescriptorParser.readDescriptors(type);
398 map.put(type, descriptors);
399 }
400
401 return map;
402 }
403
404 private void addProperties(
405 final Map<Class<?>, List<PropertyDescriptor>> descriptorMap,
406 final MultiSourceProperties compositeProperties)
407 {
408 final Properties properties = new Properties();
409
410 for (final Entry<Class<?>, List<PropertyDescriptor>> entry : descriptorMap
411 .entrySet())
412 {
413 final List<PropertyDescriptor> descriptors = entry.getValue();
414
415 for (final PropertyDescriptor descriptor : descriptors)
416 {
417 final String propertyKey = descriptor.getKey().toString();
418 final Property property = compositeProperties.getValue(propertyKey);
419 if (property != null && property.getValue() != null)
420 {
421 properties.put(propertyKey, property);
422 }
423 }
424
425 final ConfigurationKey key = compositeProperties.getConfigurationKey();
426 final ConfigurationPropertiesManagement configuration =
427 factoryCache.ensureManagement(key);
428 configuration.addDefinitions(properties);
429 }
430 }
431
432
433
434
435
436
437
438
439
440 public void addDefaultRootUrls(final PropertyDescriptorRegistry registry)
441 throws NullPointerException
442 {
443 Arguments.checkNotNull("registry", registry);
444
445 addBootRootUrls(registry);
446 addRootUrl(Thread.currentThread().getContextClassLoader());
447 }
448
449 private void addBootRootUrls(final PropertyDescriptorRegistry registry)
450 {
451 final BootConfigurationProperties initBootConfiguration =
452 new BootConfigurationProperties(registry);
453 final BootLoader bootLoader =
454 new BootLoader(initBootConfiguration, Thread.currentThread()
455 .getContextClassLoader());
456 final ConfigurationPropertiesManagement bootConfiguration =
457 bootLoader.loadAndValidate();
458 final BootProperties confProperties =
459 bootConfiguration.getProperties(BootProperties.class);
460 final List<URL> urls = confProperties.additionalPropertiesLocations();
461 if (urls != null)
462 {
463 for (final URL url : urls)
464 {
465 final URL normalized = normalize(url);
466 addRootUrl(normalized);
467 }
468 }
469 }
470
471 private static URL normalize(final URL url)
472 {
473 final String urlString = url.toExternalForm();
474 if (urlString.indexOf(urlString.length() - 1) != '/')
475 {
476 try
477 {
478 return new URL(urlString + '/');
479 }
480 catch (final MalformedURLException e)
481 {
482 LOG.warn("Cannot append '/' to '" + urlString
483 + "' for normalization. Using unnormalized URL instead.", e);
484 }
485 }
486 return url;
487 }
488
489
490
491
492
493
494
495
496
497 public void addRootUrls(final List<URL> rootLocations)
498 {
499 this.rootUrls.addAll(rootLocations);
500 }
501
502
503
504
505
506
507
508
509
510
511
512
513 public void addRootProperties(
514 final List<PropertyProvider> rootPropertyProviders)
515 {
516 this.rootPropertyProviders.addAll(rootPropertyProviders);
517 }
518
519
520
521 }