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.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
65
66
67
68 @NotThreadSafe
69 public final class ClassPathLoader<T extends ConfigurationPropertiesManagement>
70 {
71
72
73
74
75
76
77
78 private static final Logger LOG = LoggerFactory
79 .getLogger(ClassPathLoader.class);
80
81
82
83
84
85
86 private final FactoryCache<T> factoryCache;
87
88
89
90
91
92 private final Collection<URL> rootUrls = new ArrayList<URL>();
93
94
95
96
97
98 private final Collection<URL> additionalRootUrls = new ArrayList<URL>();
99
100
101
102
103
104 private final Collection<PropertyProvider> rootPropertyProviders =
105 new ArrayList<PropertyProvider>();
106
107
108
109
110
111 private final boolean lenient;
112
113
114
115
116
117 private final boolean skipPropertyLoading;
118
119
120
121
122 private final PropertyValueSecurity decrypter;
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141 public ClassPathLoader(final FactoryCache<T> factoryCache,
142 final boolean lenient, final boolean skipPropertyLoading,
143 final PropertyValueSecurity decrypter) throws NullArgumentException
144 {
145 this.factoryCache = Arg.checkNotNull("factoryCache", factoryCache);
146 this.lenient = lenient;
147 this.skipPropertyLoading = skipPropertyLoading;
148 this.decrypter = Arg.checkNotNull("decrypter", decrypter);
149 }
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165 public void addRootUrl(final URL rootUrl) throws NullArgumentException
166 {
167 Arg.checkNotNull("rootUrl", rootUrl);
168
169 if (!rootUrls.contains(rootUrl))
170 {
171 rootUrls.add(rootUrl);
172 }
173 }
174
175
176
177
178
179
180
181
182 public void addRootUrl(final Class<?> exemplar) throws NullArgumentException
183 {
184 Arg.checkNotNull("exemplar", exemplar);
185 addRootUrl(Thread.currentThread().getContextClassLoader());
186 }
187
188
189
190
191
192
193
194
195
196
197
198
199
200 public void addRootUrl(final ClassLoader classLoader)
201 throws NullArgumentException
202 {
203 Arg.checkNotNull("classLoader", classLoader);
204
205 try
206 {
207 addResources(classLoader);
208 }
209 catch (final IOException e)
210 {
211 LOG.warn("Cannot determine class path roots for the given class loader.",
212 e);
213 }
214 }
215
216 private void addResources(final ClassLoader classLoader) throws IOException
217 {
218 for (final URL url : Collections.list(UrlUtil.getResourceUrlsFromFolder(
219 classLoader, PropertiesContext.META_INF_HOME)))
220 {
221 addRootUrl(url);
222 }
223 }
224
225
226
227
228
229
230
231
232
233
234 public ConfigurationRepositoryManagement load()
235 throws CompoundConfigurationException
236 {
237 final Set<Class<?>> propertyDescriptorTypes = loadPropertyDescriptors();
238 final Map<Class<?>, List<PropertyDescriptor>> descriptors =
239 calcDescriptors(propertyDescriptorTypes);
240 factoryCache.registerDescriptors(descriptors);
241
242 final MultiSourcePropertiesManager propertiesManager = loadProperties();
243 try
244 {
245 for (final MultiSourceProperties properties : propertiesManager
246 .getProperties())
247 {
248 final List<ConfigurationException> exceptions =
249 properties.getExceptions();
250 if (!exceptions.isEmpty())
251 {
252 throw new CompoundConfigurationException(
253 properties.getConfigurationKey(), exceptions);
254 }
255
256 addProperties(descriptors, properties);
257 }
258 }
259 finally
260 {
261 propertiesManager.release();
262 }
263
264 return factoryCache.getCache();
265 }
266
267 private Set<Class<?>> loadPropertyDescriptors()
268 {
269 final PropertySetClassesLoader loader = new PropertySetClassesLoader();
270 final Set<Class<?>> propertyDescriptorTypes =
271 loader.getPropertySetTypes(rootUrls);
272 return propertyDescriptorTypes;
273 }
274
275 private MultiSourcePropertiesManager loadProperties()
276 {
277 final MultiSourcePropertiesManager allPropertiesManager =
278 new MultiSourcePropertiesManager(lenient, rootPropertyProviders);
279
280 loadProperties(allPropertiesManager);
281
282 if (skipPropertyLoading)
283 {
284 allPropertiesManager.create();
285 }
286
287 return allPropertiesManager;
288 }
289
290 private void loadProperties(
291 final MultiSourcePropertiesManager allPropertiesManager)
292 {
293 final Collection<URL> propertiesUrls = createPropertiesUrls();
294 if (!propertiesUrls.isEmpty())
295 {
296 final PropertiesFilesLoader loader = new PropertiesFilesLoader();
297 LOG.debug("Loading properties/Root location URLs: {}", propertiesUrls);
298 final Set<String> propertiesFiles =
299 loader.getPropertiesFiles(propertiesUrls);
300
301 final ClassLoader classLoader =
302 new URLClassLoader(propertiesUrls.toArray(new URL[propertiesUrls
303 .size()]), Thread.currentThread().getContextClassLoader());
304 for (final String propertiesFile : propertiesFiles)
305 {
306 if (propertiesFile.contains("META-INF"))
307 {
308 continue;
309 }
310
311 final ClassPathContext context =
312 ClassLoaderUtils
313 .createClassPathContext(classLoader, propertiesFile);
314
315 if (!isDefintionsArchive(context))
316 {
317 continue;
318 }
319
320 final DefinitionKeyHelper helper =
321 allPropertiesManager.getDefinition(context);
322 if (helper != null)
323 {
324 addProperties(allPropertiesManager, classLoader, propertiesFile,
325 helper);
326 }
327 else
328 {
329 LOG.warn("Skipping '" + propertiesFile + "' since no '"
330 + PropertiesContext.META_INF_HOME + "' provided.");
331 }
332 }
333 }
334 }
335
336 private static boolean isDefintionsArchive(final ClassPathContext context)
337 {
338 final URL url = context.getResource(PropertiesContext.DEFINITION_FILE);
339 return url != null;
340 }
341
342 private Collection<URL> createPropertiesUrls()
343 {
344 if (skipPropertyLoading)
345 {
346 return this.additionalRootUrls;
347 }
348 else
349 {
350 final Collection<URL> urls =
351 new ArrayList<URL>(additionalRootUrls.size() + rootUrls.size());
352 urls.addAll(additionalRootUrls);
353 urls.addAll(rootUrls);
354 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 final ConfigurationKey<?> key = helper.parse(propertiesFile);
364 final MultiSourceProperties allProperties =
365 allPropertiesManager.create(key);
366
367 final PropertyLocation location =
368 new PropertyLocationHelper().createPropertyLocation(classLoader,
369 propertiesFile);
370 final Properties properties = loadProperties(classLoader, propertiesFile);
371 allProperties.add(location, properties);
372 }
373
374 private Properties loadProperties(final ClassLoader classLoader,
375 final String propertiesFile)
376 {
377
378 final Properties properties = new Properties();
379 InputStream in = classLoader.getResourceAsStream(propertiesFile);
380 try
381 {
382 if (in != null)
383 {
384 in = new BufferedInputStream(in);
385 properties.load(in);
386 }
387 else
388 {
389 LOG.warn("Cannot find properties '" + propertiesFile
390 + "' in class path.");
391 }
392 }
393 catch (final IOException e)
394 {
395 LOG.warn("Cannot load properties from '" + propertiesFile + "'.");
396 }
397 finally
398 {
399 IOUtils.closeQuietly(in);
400 }
401
402 return properties;
403 }
404
405 private Map<Class<?>, List<PropertyDescriptor>> calcDescriptors(
406 final Set<Class<?>> propertyDescriptorTypes)
407 {
408 final Map<Class<?>, List<PropertyDescriptor>> map =
409 new HashMap<Class<?>, List<PropertyDescriptor>>();
410
411 for (final Class<?> type : propertyDescriptorTypes)
412 {
413 final PropertiesContext context = factoryCache.getContext(type);
414 if (context == null)
415 {
416 LOG.debug("Cannot find context for type '" + type.getName()
417 + "'. Skipping.");
418 continue;
419 }
420 final PropertyMetaDataParser propertyDescriptorParser =
421 PropertyMetaDataParser.create(context);
422 final List<PropertyDescriptor> descriptors =
423 propertyDescriptorParser.readDescriptors(type);
424 map.put(type, descriptors);
425 }
426
427 return map;
428 }
429
430 private void addProperties(
431 final Map<Class<?>, List<PropertyDescriptor>> descriptorMap,
432 final MultiSourceProperties compositeProperties)
433 {
434 final Properties properties = new Properties();
435 for (final Entry<Class<?>, List<PropertyDescriptor>> entry : descriptorMap
436 .entrySet())
437 {
438 final List<PropertyDescriptor> descriptors = entry.getValue();
439
440 for (final PropertyDescriptor descriptor : descriptors)
441 {
442 final String propertyKey = descriptor.getKey().toString();
443 final Property property =
444 compositeProperties.getValue(propertyKey, true);
445 if (property != null && property.getValue() != null)
446 {
447 properties.put(propertyKey, property);
448 }
449 }
450 }
451 final ConfigurationKey<?> key = compositeProperties.getConfigurationKey();
452 final ConfigurationPropertiesManagement configuration =
453 factoryCache.ensureManagement(key);
454 final PropertyProvider provider =
455 new PropertiesPropertyProvider(key, new PropertyLocation(
456 "classpath-various"), properties);
457 configuration.addDefinitions(provider);
458 }
459
460
461
462
463
464 public void addDefaultRootUrls()
465 {
466 addBootRootUrls();
467 addRootUrl(Thread.currentThread().getContextClassLoader());
468 }
469
470 private void addBootRootUrls()
471 {
472 final PropertyDescriptorRegistry registry =
473 new InMemoryPropertyDescriptorRegistry();
474 final BootConfigurationProperties initBootConfiguration =
475 new BootConfigurationProperties(registry, decrypter);
476 final BootLoader bootLoader =
477 new BootLoader(initBootConfiguration, Thread.currentThread()
478 .getContextClassLoader());
479 final ConfigurationPropertiesManagement bootConfiguration =
480 bootLoader.loadAndValidate();
481 final BootProperties confProperties =
482 bootConfiguration.getProperties(BootProperties.class);
483 final List<URL> urls = confProperties.additionalPropertiesLocations();
484 if (urls != null)
485 {
486 for (final URL url : urls)
487 {
488 final URL normalized = normalize(url);
489 addAdditionalRootUrl(normalized);
490 }
491 }
492 }
493
494 private void addAdditionalRootUrl(final URL rootUrl)
495 {
496 Arg.checkNotNull("rootUrl", rootUrl);
497
498 if (!additionalRootUrls.contains(rootUrl))
499 {
500 additionalRootUrls.add(rootUrl);
501 }
502 }
503
504 private static URL normalize(final URL url)
505 {
506 final String urlString = url.toExternalForm();
507 if (urlString.indexOf(urlString.length() - 1) != '/')
508 {
509 try
510 {
511 return new URL(urlString + '/');
512 }
513 catch (final MalformedURLException e)
514 {
515 LOG.warn("Cannot append '/' to '" + urlString
516 + "' for normalization. Using unnormalized URL instead.", e);
517 }
518 }
519 return url;
520 }
521
522
523
524
525
526
527
528
529
530 public void addRootUrls(final List<URL> rootLocations)
531 {
532 this.rootUrls.addAll(rootLocations);
533 }
534
535
536
537
538
539
540
541
542
543
544
545
546 public void addRootProperties(
547 final List<PropertyProvider> rootPropertyProviders)
548 {
549 this.rootPropertyProviders.addAll(rootPropertyProviders);
550 }
551
552
553
554
555
556
557
558
559 @Override
560 public String toString()
561 {
562 final StringBuilder buffer = new StringBuilder();
563
564 final char returnChar = '\n';
565
566 buffer.append("== Root URLs:");
567 for (final URL url : rootUrls)
568 {
569 buffer.append(returnChar).append(" ").append(url.toExternalForm());
570 }
571
572 buffer.append(returnChar).append("== Additional root URLs:");
573 for (final URL url : additionalRootUrls)
574 {
575 buffer.append(returnChar).append(" ").append(url.toExternalForm());
576 }
577
578 buffer.append(returnChar).append("\n== Root Property Providers:");
579 for (final PropertyProvider provider : rootPropertyProviders)
580 {
581 buffer.append(returnChar).append(" ").append(provider);
582 }
583
584 buffer.append(returnChar).append("== Factory Cache:\n");
585 buffer.append(factoryCache);
586
587 buffer.append(returnChar).append("== Properties:");
588 buffer.append(returnChar).append(" Lenient validation : ")
589 .append(lenient);
590 buffer.append(returnChar).append(" Skip propertyLoading: ")
591 .append(skipPropertyLoading);
592
593 return buffer.toString();
594 }
595 }