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