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.IOException;
19  import java.io.ObjectInputStream;
20  import java.net.URL;
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.Collection;
24  import java.util.LinkedHashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.concurrent.locks.Lock;
28  import java.util.concurrent.locks.ReentrantReadWriteLock;
29  
30  import javax.annotation.concurrent.ThreadSafe;
31  
32  import de.smartics.properties.api.config.app.FactoryConfiguration;
33  import de.smartics.properties.api.config.domain.CompoundConfigurationException;
34  import de.smartics.properties.api.config.domain.ConfigurationCode;
35  import de.smartics.properties.api.config.domain.ConfigurationException;
36  import de.smartics.properties.api.config.domain.ConfigurationProperties;
37  import de.smartics.properties.api.config.domain.ConfigurationPropertiesManagement;
38  import de.smartics.properties.api.config.domain.ConfigurationRepositoryManagement;
39  import de.smartics.properties.api.config.domain.PropertyProvider;
40  import de.smartics.properties.api.config.domain.key.ConfigurationKey;
41  import de.smartics.properties.api.core.domain.PropertyDescriptorRegistry;
42  import de.smartics.properties.resource.domain.ArtifactId;
43  import de.smartics.properties.resource.domain.ArtifactRef;
44  import de.smartics.properties.resource.domain.ClassPathEnvironment;
45  import de.smartics.properties.resource.repository.RepositoryException;
46  import de.smartics.properties.resource.repository.ResourceRepository;
47  import de.smartics.properties.resource.repository.ResourceRepositoryFactory;
48  import de.smartics.properties.spi.config.domain.key.ConfigurationKeyContextManager;
49  import de.smartics.properties.spi.core.registry.InMemoryPropertyDescriptorRegistry;
50  import de.smartics.util.lang.Arg;
51  import de.smartics.util.lang.NullArgumentException;
52  
53  /**
54   * Base implementation of the {@link ConfigurationPropertiesManagementFactory}.
55   *
56   * @param <T> the concrete type of the returned configuration properties.
57   */
58  @ThreadSafe
59  public abstract class AbstractConfigurationPropertiesFactory<T extends ConfigurationPropertiesManagement>
60      implements ConfigurationPropertiesManagementFactory
61  { // NOPMD
62    // ********************************* Fields *********************************
63  
64    // --- constants ------------------------------------------------------------
65  
66    /**
67     * The class version identifier.
68     */
69    private static final long serialVersionUID = 1L;
70  
71    // --- members --------------------------------------------------------------
72  
73    /**
74     * The locations to search for property descriptors and definitions.
75     *
76     * @serial
77     */
78    private final List<URL> rootLocations = new ArrayList<URL>();
79  
80    /**
81     * The properties provided by other means than being found on the class path.
82     * <p>
83     * While
84     * {@link AbstractConfigurationPropertiesFactory#addRootLocations(Collection)}
85     * and {@link AbstractConfigurationPropertiesFactory#addRootLocations(URL...)}
86     * allows to declare class path roots to search for properties files
87     * automatically, the root property provider allow access to properties that
88     * are stored in arbitrary locations and formats. These properties may be
89     * stored in databases or any other technology, as long as they provide the
90     * concept of key and value.
91     * </p>
92     *
93     * @serial
94     */
95    private final List<PropertyProvider> rootPropertyProviders =
96        new ArrayList<PropertyProvider>();
97  
98    /**
99     * The factory configuration.
100    *
101    * @serial
102    */
103   private final FactoryConfiguration factoryConfiguration =
104       new FactoryConfiguration();
105 
106   /**
107    * The list of artifact that reference all resources to access to read
108    * property declarations and definitions.
109    *
110    * @serial
111    */
112   private final Map<ArtifactId, ClassPathEnvironment> artifactIds =
113       new LinkedHashMap<ArtifactId, ClassPathEnvironment>();
114 
115   /**
116    * The cache of currently loaded configurations.
117    *
118    * @impl the implementation of the cache is required to be thread-safe.
119    */
120   private transient ConfigurationRepositoryManagement cache;
121 
122   /**
123    * The registry of declarations used by all created configurations.
124    *
125    * @serial
126    */
127   private final PropertyDescriptorRegistry registry =
128       new InMemoryPropertyDescriptorRegistry();
129 
130   /**
131    * The lock for synchronized access.
132    *
133    * @serial
134    */
135   private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
136 
137   /**
138    * The read lock for synchronized access.
139    *
140    * @serial
141    */
142   private final Lock readLock = lock.readLock();
143 
144   /**
145    * The write lock for synchronized access.
146    *
147    * @serial
148    */
149   private final Lock writeLock = lock.writeLock();
150 
151   /**
152    * Flag to check if initialization has already taken place.
153    *
154    * @serial
155    */
156   private volatile boolean initialized;
157 
158   // ****************************** Initializer *******************************
159 
160   // ****************************** Constructors ******************************
161 
162   /**
163    * Default constructor.
164    */
165   protected AbstractConfigurationPropertiesFactory()
166   {
167     this.cache =
168         new InMemoryConfigurationRepositoryManagement(registry, createFactory()); // NOPMD
169   }
170 
171   // CHECKSTYLE:OFF
172   private ConfigurationPropertiesManagementFactory createFactory()
173   {
174     return new ConfigurationPropertiesManagementFactory()
175     {
176       private static final long serialVersionUID = 1L;
177 
178       @Override
179       public final ConfigurationPropertiesManagement create(
180           final ConfigurationKey<?> key)
181       {
182         return AbstractConfigurationPropertiesFactory.this
183             .createNewConfiguration(key);
184       }
185 
186       @Override
187       public ConfigurationPropertiesManagement remove(
188           final ConfigurationKey<?> key) throws NullPointerException
189       {
190         return AbstractConfigurationPropertiesFactory.this.remove(key);
191       }
192 
193       @Override
194       public FactoryConfiguration getFactoryConfiguration()
195       {
196         return AbstractConfigurationPropertiesFactory.this
197             .getFactoryConfiguration();
198       }
199 
200       @Override
201       public void addRootLocations(final Collection<URL> urls)
202       {
203         AbstractConfigurationPropertiesFactory.this.addRootLocations(urls);
204       }
205 
206       @Override
207       public void addRootLocations(final URL... urls)
208       {
209         AbstractConfigurationPropertiesFactory.this.addRootLocations(urls);
210       }
211 
212       @Override
213       public void addPropertyProviders(
214           final Collection<PropertyProvider> providers)
215       {
216         AbstractConfigurationPropertiesFactory.this
217             .addPropertyProviders(providers);
218       }
219 
220       @Override
221       public void addPropertyProviders(final PropertyProvider... providers)
222       {
223         AbstractConfigurationPropertiesFactory.this
224             .addPropertyProviders(providers);
225       }
226 
227       @Override
228       public ConfigurationPropertiesManagement createManagement(
229           final ConfigurationKey<?> key) throws NullPointerException,
230         ConfigurationException
231       {
232         return AbstractConfigurationPropertiesFactory.this
233             .createManagement(key);
234       }
235 
236       @Override
237       public ConfigurationProperties createDefault()
238         throws ConfigurationException
239       {
240         return AbstractConfigurationPropertiesFactory.this.createDefault();
241       }
242 
243       @Override
244       public ConfigurationPropertiesManagement createDefaultManagement()
245         throws ConfigurationException
246       {
247         return AbstractConfigurationPropertiesFactory.this
248             .createDefaultManagement();
249       }
250 
251       @Override
252       public Collection<ConfigurationKey<?>> getRegisteredConfigurationKeys()
253       {
254         return AbstractConfigurationPropertiesFactory.this
255             .getRegisteredConfigurationKeys();
256       }
257 
258       @Override
259       public PropertyDescriptorRegistry getRegistry()
260       {
261         return AbstractConfigurationPropertiesFactory.this.getRegistry();
262       }
263 
264       @Override
265       public Collection<ConfigurationKey<?>> materialize()
266       {
267         return AbstractConfigurationPropertiesFactory.this.materialize();
268       }
269 
270       @Override
271       public void release()
272       {
273         AbstractConfigurationPropertiesFactory.this.release();
274       }
275 
276       @Override
277       public String addRootUrls(final ArtifactId artifactId)
278         throws NullArgumentException, RepositoryException,
279         CompoundConfigurationException
280       {
281         return AbstractConfigurationPropertiesFactory.this
282             .addRootUrls(artifactId);
283       }
284 
285       @Override
286       public ArtifactRef getArtifactRef(final String artifactId)
287         throws NullPointerException
288       {
289         return AbstractConfigurationPropertiesFactory.this
290             .getArtifactRef(artifactId);
291       }
292     };
293   }
294 
295   // CHECKSTYLE:ON
296 
297   // ****************************** Inner Classes *****************************
298 
299   // ********************************* Methods ********************************
300 
301   // --- init -----------------------------------------------------------------
302 
303   // --- get&set --------------------------------------------------------------
304 
305   @Override
306   public final PropertyDescriptorRegistry getRegistry()
307   {
308     return registry;
309   }
310 
311   @Override
312   public final FactoryConfiguration getFactoryConfiguration()
313   {
314     return factoryConfiguration;
315   }
316 
317   @Override
318   public final void addRootLocations(final Collection<URL> urls)
319   {
320     if (urls != null && !urls.isEmpty())
321     {
322       writeLock.lock();
323       try
324       {
325         rootLocations.addAll(urls);
326       }
327       finally
328       {
329         writeLock.unlock();
330       }
331     }
332   }
333 
334   @Override
335   public final void addRootLocations(final URL... urls)
336   {
337     if (urls != null && urls.length != 0)
338     {
339       writeLock.lock();
340       try
341       {
342         rootLocations.addAll(Arrays.asList(urls));
343       }
344       finally
345       {
346         writeLock.unlock();
347       }
348     }
349   }
350 
351   @Override
352   public final void addPropertyProviders(
353       final Collection<PropertyProvider> providers)
354   {
355     if (providers != null && !providers.isEmpty())
356     {
357       writeLock.lock();
358       try
359       {
360         rootPropertyProviders.addAll(providers);
361       }
362       finally
363       {
364         writeLock.unlock();
365       }
366     }
367   }
368 
369   @Override
370   public final void addPropertyProviders(final PropertyProvider... providers)
371   {
372     if (providers != null && providers.length != 0)
373     {
374       writeLock.lock();
375       try
376       {
377         rootPropertyProviders.addAll(Arrays.asList(providers));
378       }
379       finally
380       {
381         writeLock.unlock();
382       }
383     }
384   }
385 
386   // --- business -------------------------------------------------------------
387 
388   @Override
389   public final String addRootUrls(final ArtifactId artifactId)
390     throws NullArgumentException, RepositoryException,
391     CompoundConfigurationException
392   {
393     Arg.checkNotNull("artifactId", artifactId);
394 
395     final ResourceRepositoryFactory factory = new ResourceRepositoryFactory();
396     final ResourceRepository repository = factory.create();
397     final ClassPathEnvironment resources = repository.resolve(artifactId);
398 
399     synchronized (artifactIds)
400     {
401       artifactIds.put(artifactId, resources);
402     }
403 
404     final List<URL> urls = resources.getUrls();
405     addRootLocations(urls);
406 
407     return repository.getRemoteRepositoryUrl();
408   }
409 
410   @Override
411   public final ArtifactRef getArtifactRef(final String artifactId)
412     throws NullPointerException
413   {
414     Arg.checkNotNull("artifactId", artifactId);
415 
416     for (final ClassPathEnvironment env : artifactIds.values())
417     {
418       final ArtifactRef ref = env.getArtifactRef(artifactId);
419       if (ref != null)
420       {
421         return ref;
422       }
423     }
424 
425     return null;
426   }
427 
428   @Override
429   public final Collection<ConfigurationKey<?>> getRegisteredConfigurationKeys()
430   {
431     return cache.getKeys();
432   }
433 
434   @Override
435   public final T create(final ConfigurationKey<?> key)
436     throws NullPointerException, ConfigurationException
437   {
438     return createManagement(key);
439   }
440 
441   @Override
442   public final T createDefault()
443   {
444     final ConfigurationKey<?> key =
445         ConfigurationKeyContextManager.INSTANCE.context()
446             .configurationKeyFactory().createDefaultKey();
447     return create(key);
448   }
449 
450   @Override
451   public final ConfigurationPropertiesManagement createDefaultManagement()
452   {
453     final ConfigurationKey<?> key =
454         ConfigurationKeyContextManager.INSTANCE.context()
455             .configurationKeyFactory().createDefaultKey();
456     return createManagement(key);
457   }
458 
459   @Override
460   @SuppressWarnings("unchecked")
461   public final T createManagement(final ConfigurationKey<?> key)
462     throws NullPointerException, ConfigurationException
463   {
464     writeLock.lock();
465     try
466     {
467       // FIXME: If our cache contains different implementations, this cast won't
468       // work.
469       return (T) getCachedOrCreate(key);
470     }
471     finally
472     {
473       writeLock.unlock();
474     }
475   }
476 
477   private ConfigurationPropertiesManagement getCachedOrCreate(
478       final ConfigurationKey<?> key)
479   {
480     ConfigurationPropertiesManagement configuration = null;
481 
482     if (!cache.hasPropertiesManagement(key))
483     {
484       configuration = createNewConfiguration(key);
485     }
486 
487     initializeConfiguration();
488 
489     final ConfigurationPropertiesManagement withDefaults =
490         cache.getPropertiesManagementWithDefaults(key);
491 
492     if (withDefaults != null)
493     {
494       configuration = withDefaults;
495     }
496     else if (configuration != null)
497     {
498       // Getting ...WithDefaults may return null in case no configuration
499       // properties have been found. So we return an empty configuration instead
500       // of 'null'.
501       cache.registerProperties(key, configuration);
502     }
503     else
504     {
505       throw new ConfigurationException(
506           ConfigurationCode.CONFIGURATION_ACCESS_FAILED, key);
507     }
508 
509     return configuration;
510   }
511 
512   private void initializeConfiguration()
513   {
514     if (!initialized)
515     {
516       final FactoryCache<T> factoryCache = new FactoryCache<T>(cache, this);
517 
518       final ClassPathLoader<T> loader =
519           new ClassPathLoader<T>(factoryCache, false,
520               factoryConfiguration.isSkipClassPathPropertyLoading(),
521               factoryConfiguration.getDecrypter());
522 
523       readLock.lock();
524       try
525       {
526         loader.addRootUrls(rootLocations);
527         loader.addRootProperties(rootPropertyProviders);
528       }
529       finally
530       {
531         readLock.unlock();
532       }
533 
534       if (factoryConfiguration.isAddDefaultRootLocations())
535       {
536         loader.addDefaultRootUrls();
537       }
538       loader.load();
539 
540       initialized = true;
541     }
542   }
543 
544   @Override
545   public Collection<ConfigurationKey<?>> materialize()
546   {
547     writeLock.lock();
548     try
549     {
550       initializeConfiguration();
551     }
552     finally
553     {
554       writeLock.unlock();
555     }
556 
557     final Collection<ConfigurationKey<?>> keys =
558         getRegisteredConfigurationKeys();
559     return keys;
560   }
561 
562   /**
563    * Creates an empty instance of the configuration properties instance. Where
564    * the public create methods may consult a cache, this method is required to
565    * create a new instance.
566    *
567    * @param key the key to the instance.
568    * @return the instance. Never <code>null</code>.
569    * @throws NullPointerException if {@code key} is <code>null</code>.
570    * @throws ConfigurationException if the configuration cannot be created.
571    */
572   protected abstract T createNewConfiguration(ConfigurationKey<?> key)
573     throws NullPointerException, ConfigurationException;
574 
575   // --- object basics --------------------------------------------------------
576 
577   @Override
578   public void release()
579   {
580     cache.release();
581   }
582 
583   @Override
584   public final ConfigurationPropertiesManagement remove(
585       final ConfigurationKey<?> key) throws NullPointerException
586   {
587     return cache.deregisterProperties(key);
588   }
589 
590   /**
591    * Reads the object from the given stream.
592    *
593    * @param in the stream to read from.
594    * @throws IOException on read problems.
595    * @throws ClassNotFoundException if a class cannot be found.
596    */
597   private void readObject(final ObjectInputStream in) throws IOException,
598     ClassNotFoundException
599   {
600     in.defaultReadObject();
601 
602     cache =
603         new InMemoryConfigurationRepositoryManagement(registry, createFactory());
604   }
605 
606   @Override
607   public String toString()
608   {
609     final StringBuilder buffer = new StringBuilder(8192);
610     final Collection<ConfigurationKey<?>> keys = cache.getKeys();
611     buffer.append("The factory caches access to the following " + keys.size()
612                   + " configurations:");
613     for (final ConfigurationKey<?> key : keys)
614     {
615       buffer.append("\n  ").append(key);
616     }
617     buffer.append("\nDetails:");
618     for (final ConfigurationKey<?> key : keys)
619     {
620       final ConfigurationProperties config = cache.getProperties(key);
621       buffer.append("\n--> ").append(config);
622     }
623 
624     synchronized (artifactIds)
625     {
626       for (final ArtifactId artifactId : artifactIds.keySet())
627       {
628         buffer.append(artifactId).append(' ');
629       }
630     }
631 
632     return buffer.toString();
633   }
634 }