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(classLoader
219 .getResources(PropertiesContext.META_INF_HOME)))
220 {
221 final URL rootUrl = truncateUrl(url);
222 addRootUrl(rootUrl);
223 }
224 }
225
226 private static URL truncateUrl(final URL url)
227 {
228 final String urlString = url.toExternalForm();
229 final boolean isJar = "jar".equals(url.getProtocol());
230 final int last;
231 if (isJar)
232 {
233 last = urlString.indexOf('!') + 2;
234 }
235 else
236 {
237 last = urlString.length() - PropertiesContext.META_INF_HOME.length() - 1;
238 }
239
240 final String urlStringTruncated = urlString.substring(0, last);
241 URL urlTruncated;
242 try
243 {
244 urlTruncated = new URL(urlStringTruncated);
245 return urlTruncated;
246 }
247 catch (final MalformedURLException e)
248 {
249 LOG.warn("Cannot use URL '{}' in its truncated form '{}'.", url,
250 urlStringTruncated);
251 return url;
252 }
253 }
254
255
256
257
258
259
260
261
262
263
264 public ConfigurationRepositoryManagement load()
265 throws CompoundConfigurationException
266 {
267 final Set<Class<?>> propertyDescriptorTypes = loadPropertyDescriptors();
268 final Map<Class<?>, List<PropertyDescriptor>> descriptors =
269 calcDescriptors(propertyDescriptorTypes);
270 factoryCache.registerDescriptors(descriptors);
271
272 final MultiSourcePropertiesManager propertiesManager = loadProperties();
273 try
274 {
275 for (final MultiSourceProperties properties : propertiesManager
276 .getProperties())
277 {
278 final List<ConfigurationException> exceptions =
279 properties.getExceptions();
280 if (!exceptions.isEmpty())
281 {
282 throw new CompoundConfigurationException(
283 properties.getConfigurationKey(), exceptions);
284 }
285
286 addProperties(descriptors, properties);
287 }
288 }
289 finally
290 {
291 propertiesManager.release();
292 }
293
294 return factoryCache.getCache();
295 }
296
297 private Set<Class<?>> loadPropertyDescriptors()
298 {
299 final PropertySetClassesLoader loader = new PropertySetClassesLoader();
300 final Set<Class<?>> propertyDescriptorTypes =
301 loader.getPropertySetTypes(rootUrls);
302 return propertyDescriptorTypes;
303 }
304
305 private MultiSourcePropertiesManager loadProperties()
306 {
307 final MultiSourcePropertiesManager allPropertiesManager =
308 new MultiSourcePropertiesManager(lenient, rootPropertyProviders);
309
310 loadProperties(allPropertiesManager);
311
312 if (skipPropertyLoading)
313 {
314 allPropertiesManager.create();
315 }
316
317 return allPropertiesManager;
318 }
319
320 private void loadProperties(
321 final MultiSourcePropertiesManager allPropertiesManager)
322 {
323 final Collection<URL> propertiesUrls = createPropertiesUrls();
324 if (!propertiesUrls.isEmpty())
325 {
326 final PropertiesFilesLoader loader = new PropertiesFilesLoader();
327 LOG.debug("Loading properties/Root location URLs: {}", propertiesUrls);
328 final Set<String> propertiesFiles =
329 loader.getPropertiesFiles(propertiesUrls);
330
331 final ClassLoader classLoader =
332 new URLClassLoader(propertiesUrls.toArray(new URL[propertiesUrls
333 .size()]), Thread.currentThread().getContextClassLoader());
334 for (final String propertiesFile : propertiesFiles)
335 {
336 if (propertiesFile.contains("META-INF"))
337 {
338 continue;
339 }
340
341 final ClassPathContext context =
342 ClassLoaderUtils
343 .createClassPathContext(classLoader, propertiesFile);
344
345 if (!isDefintionsArchive(context))
346 {
347 continue;
348 }
349
350 final DefinitionKeyHelper helper =
351 allPropertiesManager.getDefinition(context);
352 if (helper != null)
353 {
354 addProperties(allPropertiesManager, classLoader, propertiesFile,
355 helper);
356 }
357 else
358 {
359 LOG.warn("Skipping '" + propertiesFile + "' since no '"
360 + PropertiesContext.META_INF_HOME + "' provided.");
361 }
362 }
363 }
364 }
365
366 private static boolean isDefintionsArchive(final ClassPathContext context)
367 {
368 final URL url = context.getResource(PropertiesContext.DEFINITION_FILE);
369 return url != null;
370 }
371
372 private Collection<URL> createPropertiesUrls()
373 {
374 if (skipPropertyLoading)
375 {
376 return this.additionalRootUrls;
377 }
378 else
379 {
380 final Collection<URL> urls =
381 new ArrayList<URL>(additionalRootUrls.size() + rootUrls.size());
382 urls.addAll(additionalRootUrls);
383 urls.addAll(rootUrls);
384 return urls;
385 }
386 }
387
388 private void addProperties(
389 final MultiSourcePropertiesManager allPropertiesManager,
390 final ClassLoader classLoader, final String propertiesFile,
391 final DefinitionKeyHelper helper) throws IllegalArgumentException
392 {
393 final ConfigurationKey<?> key = helper.parse(propertiesFile);
394 final MultiSourceProperties allProperties =
395 allPropertiesManager.create(key);
396
397 final PropertyLocation location =
398 new PropertyLocationHelper().createPropertyLocation(classLoader,
399 propertiesFile);
400 final Properties properties = loadProperties(classLoader, propertiesFile);
401 allProperties.add(location, properties);
402 }
403
404 private Properties loadProperties(final ClassLoader classLoader,
405 final String propertiesFile)
406 {
407
408 final Properties properties = new Properties();
409 InputStream in = classLoader.getResourceAsStream(propertiesFile);
410 try
411 {
412 if (in != null)
413 {
414 in = new BufferedInputStream(in);
415 properties.load(in);
416 }
417 else
418 {
419 LOG.warn("Cannot find properties '" + propertiesFile
420 + "' in class path.");
421 }
422 }
423 catch (final IOException e)
424 {
425 LOG.warn("Cannot load properties from '" + propertiesFile + "'.");
426 }
427 finally
428 {
429 IOUtils.closeQuietly(in);
430 }
431
432 return properties;
433 }
434
435 private Map<Class<?>, List<PropertyDescriptor>> calcDescriptors(
436 final Set<Class<?>> propertyDescriptorTypes)
437 {
438 final Map<Class<?>, List<PropertyDescriptor>> map =
439 new HashMap<Class<?>, List<PropertyDescriptor>>();
440
441 for (final Class<?> type : propertyDescriptorTypes)
442 {
443 final PropertiesContext context = factoryCache.getContext(type);
444 if (context == null)
445 {
446 LOG.debug("Cannot find context for type '" + type.getName()
447 + "'. Skipping.");
448 continue;
449 }
450 final PropertyMetaDataParser propertyDescriptorParser =
451 PropertyMetaDataParser.create(context);
452 final List<PropertyDescriptor> descriptors =
453 propertyDescriptorParser.readDescriptors(type);
454 map.put(type, descriptors);
455 }
456
457 return map;
458 }
459
460 private void addProperties(
461 final Map<Class<?>, List<PropertyDescriptor>> descriptorMap,
462 final MultiSourceProperties compositeProperties)
463 {
464 final Properties properties = new Properties();
465 for (final Entry<Class<?>, List<PropertyDescriptor>> entry : descriptorMap
466 .entrySet())
467 {
468 final List<PropertyDescriptor> descriptors = entry.getValue();
469
470 for (final PropertyDescriptor descriptor : descriptors)
471 {
472 final String propertyKey = descriptor.getKey().toString();
473 final Property property =
474 compositeProperties.getValue(propertyKey, true);
475 if (property != null && property.getValue() != null)
476 {
477 properties.put(propertyKey, property);
478 }
479 }
480 }
481 final ConfigurationKey<?> key = compositeProperties.getConfigurationKey();
482 final ConfigurationPropertiesManagement configuration =
483 factoryCache.ensureManagement(key);
484 final PropertyProvider provider =
485 new PropertiesPropertyProvider(key, new PropertyLocation(
486 "classpath-various"), properties);
487 configuration.addDefinitions(provider);
488 }
489
490
491
492
493
494 public void addDefaultRootUrls()
495 {
496 addBootRootUrls();
497 addRootUrl(Thread.currentThread().getContextClassLoader());
498 }
499
500 private void addBootRootUrls()
501 {
502 final PropertyDescriptorRegistry registry =
503 new InMemoryPropertyDescriptorRegistry();
504 final BootConfigurationProperties initBootConfiguration =
505 new BootConfigurationProperties(registry, decrypter);
506 final BootLoader bootLoader =
507 new BootLoader(initBootConfiguration, Thread.currentThread()
508 .getContextClassLoader());
509 final ConfigurationPropertiesManagement bootConfiguration =
510 bootLoader.loadAndValidate();
511 final BootProperties confProperties =
512 bootConfiguration.getProperties(BootProperties.class);
513 final List<URL> urls = confProperties.additionalPropertiesLocations();
514 if (urls != null)
515 {
516 for (final URL url : urls)
517 {
518 final URL normalized = normalize(url);
519 addAdditionalRootUrl(normalized);
520 }
521 }
522 }
523
524 private void addAdditionalRootUrl(final URL rootUrl)
525 {
526 Arg.checkNotNull("rootUrl", rootUrl);
527
528 if (!additionalRootUrls.contains(rootUrl))
529 {
530 additionalRootUrls.add(rootUrl);
531 }
532 }
533
534 private static URL normalize(final URL url)
535 {
536 final String urlString = url.toExternalForm();
537 if (urlString.indexOf(urlString.length() - 1) != '/')
538 {
539 try
540 {
541 return new URL(urlString + '/');
542 }
543 catch (final MalformedURLException e)
544 {
545 LOG.warn("Cannot append '/' to '" + urlString
546 + "' for normalization. Using unnormalized URL instead.", e);
547 }
548 }
549 return url;
550 }
551
552
553
554
555
556
557
558
559
560 public void addRootUrls(final List<URL> rootLocations)
561 {
562 this.rootUrls.addAll(rootLocations);
563 }
564
565
566
567
568
569
570
571
572
573
574
575
576 public void addRootProperties(
577 final List<PropertyProvider> rootPropertyProviders)
578 {
579 this.rootPropertyProviders.addAll(rootPropertyProviders);
580 }
581
582
583
584
585
586
587
588
589 @Override
590 public String toString()
591 {
592 final StringBuilder buffer = new StringBuilder();
593
594 final char returnChar = '\n';
595
596 buffer.append("== Root URLs:");
597 for (final URL url : rootUrls)
598 {
599 buffer.append(returnChar).append(" ").append(url.toExternalForm());
600 }
601
602 buffer.append(returnChar).append("== Additional root URLs:");
603 for (final URL url : additionalRootUrls)
604 {
605 buffer.append(returnChar).append(" ").append(url.toExternalForm());
606 }
607
608 buffer.append(returnChar).append("\n== Root Property Providers:");
609 for (final PropertyProvider provider : rootPropertyProviders)
610 {
611 buffer.append(returnChar).append(" ").append(provider);
612 }
613
614 buffer.append(returnChar).append("== Factory Cache:\n");
615 buffer.append(factoryCache);
616
617 buffer.append(returnChar).append("== Properties:");
618 buffer.append(returnChar).append(" Lenient validation : ")
619 .append(lenient);
620 buffer.append(returnChar).append(" Skip propertyLoading: ")
621 .append(skipPropertyLoading);
622
623 return buffer.toString();
624 }
625 }