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.Serializable;
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.Iterator;
23  import java.util.LinkedHashMap;
24  import java.util.LinkedList;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Properties;
28  
29  import org.apache.commons.lang.ObjectUtils;
30  import org.slf4j.Logger;
31  import org.slf4j.LoggerFactory;
32  
33  import de.smartics.properties.api.config.domain.ConfigurationException;
34  import de.smartics.properties.api.config.domain.DuplicatePropertyException;
35  import de.smartics.properties.api.config.domain.Property;
36  import de.smartics.properties.api.config.domain.PropertyLocation;
37  import de.smartics.properties.api.config.domain.PropertyProvider;
38  import de.smartics.properties.api.config.domain.key.ConfigurationKey;
39  
40  /**
41   * Provides properties from multiple sources for a given configuration.
42   */
43  class MultiSourceProperties implements Serializable
44  {
45    // ********************************* Fields *********************************
46  
47    // --- constants ------------------------------------------------------------
48  
49    /**
50     * The class version identifier.
51     * <p>
52     * The value of this constant is {@value}.
53     * </p>
54     */
55    private static final long serialVersionUID = 1L;
56  
57    /**
58     * Reference to the logger for this class.
59     */
60    private static final Logger LOG = LoggerFactory
61        .getLogger(MultiSourceProperties.class);
62  
63    // --- members --------------------------------------------------------------
64  
65    /**
66     * The key to the configuration the properties are added to.
67     *
68     * @serial
69     */
70    private final ConfigurationKey<?> configurationKey;
71  
72    /**
73     * The exceptions encountered during merging properties. May be
74     * <code>null</code> if no exceptions are to be collected.
75     *
76     * @serial
77     */
78    private final List<ConfigurationException> exceptions;
79  
80    /**
81     * Maps the source of the properties to the properties instance.
82     *
83     * @serial
84     */
85    private final Map<PropertyLocation, PropertyProvider> propertiesMap =
86        new LinkedHashMap<PropertyLocation, PropertyProvider>();
87  
88    // ****************************** Initializer *******************************
89  
90    // ****************************** Constructors ******************************
91  
92    /**
93     * Convenience constructor with an empty exceptions list.
94     *
95     * @param configurationKey the key to the configuration the properties are
96     *          added to.
97     */
98    MultiSourceProperties(final ConfigurationKey<?> configurationKey)
99    {
100     this(configurationKey, new ArrayList<ConfigurationException>());
101   }
102 
103   /**
104    * Default constructor.
105    *
106    * @param configurationKey the key to the configuration the properties are
107    *          added to.
108    * @param exceptions the exceptions encountered during merging properties.
109    */
110   MultiSourceProperties(final ConfigurationKey<?> configurationKey,
111       final List<ConfigurationException> exceptions)
112   {
113     this.configurationKey = configurationKey;
114     this.exceptions = exceptions;
115   }
116 
117   // ****************************** Inner Classes *****************************
118 
119   // ********************************* Methods ********************************
120 
121   // --- init -----------------------------------------------------------------
122 
123   // --- get&set --------------------------------------------------------------
124 
125   /**
126    * Returns the key to the configuration the properties are added to.
127    *
128    * @return the key to the configuration the properties are added to.
129    */
130   public ConfigurationKey<?> getConfigurationKey()
131   {
132     return configurationKey;
133   }
134 
135   /**
136    * Returns the exceptions encountered during merging properties. May be
137    * <code>null</code> if no exceptions are to be collected.
138    *
139    * @return the exceptions encountered during merging properties.
140    */
141   public List<ConfigurationException> getExceptions()
142   {
143     if (exceptions != null)
144     {
145       return exceptions;
146     }
147     else
148     {
149       return Collections.emptyList();
150     }
151   }
152 
153   // --- business -------------------------------------------------------------
154 
155   void add(final PropertyLocation source, final Properties properties)
156   {
157     if (propertiesMap.containsKey(source))
158     {
159       LOG.warn("Properties from '{}' already added.", source);
160       return;
161     }
162 
163     for (final Iterator<Object> i = properties.keySet().iterator(); i.hasNext();)
164     {
165       final Object key = i.next();
166       final Object value = properties.get(key);
167       if (contains(source, key, value))
168       {
169         LOG.info("Duplicate property '{}' of '{}' removed.", key, source);
170         i.remove();
171       }
172     }
173 
174     final PropertiesPropertyProvider provider =
175         new PropertiesPropertyProvider(configurationKey, source, properties);
176     propertiesMap.put(source, provider);
177   }
178 
179   void addProviders(final Collection<PropertyProvider> providers)
180   {
181     for (final PropertyProvider provider : providers)
182     {
183       if (configurationKey.equals(provider.getConfigurationKey()))
184       {
185         addProvider(provider);
186       }
187     }
188   }
189 
190   void addProvider(final PropertyProvider provider)
191   {
192     final PropertyLocation source = provider.getSourceId();
193     if (propertiesMap.containsKey(source))
194     {
195       LOG.warn("Properties from '{}' already added.", source);
196       return;
197     }
198 
199     propertiesMap.put(source, provider);
200   }
201 
202   private boolean contains(final PropertyLocation newSource, final Object key,
203       final Object newValue)
204   {
205     boolean contains = false;
206     for (final Map.Entry<PropertyLocation, PropertyProvider> entry : propertiesMap
207         .entrySet())
208     {
209       final PropertyLocation currentSource = entry.getKey();
210       final PropertyProvider currentProperties = entry.getValue();
211 
212       final String name = ObjectUtils.toString(key, null);
213       if (currentProperties.containsKey(name))
214       {
215         final Property property = currentProperties.getProperty(name);
216         final String currentValue =
217             property != null ? property.getValue() : null;
218         final String currentValueString =
219             ObjectUtils.toString(currentValue, null);
220         final String newValueString = ObjectUtils.toString(newValue, null);
221         if (!ObjectUtils.equals(currentValueString, newValueString))
222         {
223           if (exceptions != null)
224           {
225             addException(newSource, currentSource, name, currentValueString,
226                 newValueString);
227           }
228           else
229           {
230             warnDuplicateWithDifferentValue(newSource, newValue, currentSource,
231                 name, currentValue);
232           }
233         }
234         else
235         {
236           warnDuplicateWithSameValue(name, currentSource, newSource,
237               currentValue);
238         }
239 
240         contains = true;
241       }
242     }
243 
244     return contains;
245   }
246 
247   private void addException(final PropertyLocation newSource,
248       final PropertyLocation currentSource, final String name,
249       final String currentValueString, final String newValueString)
250   {
251     final DuplicatePropertyException e =
252         new DuplicatePropertyException(configurationKey,
253             new Property(currentSource, ObjectUtils.toString(name, null),
254                 currentValueString), new Property(newSource,
255                 ObjectUtils.toString(name, null), newValueString));
256     exceptions.add(e);
257   }
258 
259   private void warnDuplicateWithDifferentValue(
260       final PropertyLocation newSource, final Object newValue,
261       final PropertyLocation currentSource, final String name,
262       final String currentValue)
263   {
264     LOG.warn("Duplicate key '{}' with new value '{}' in\n  '{}'."
265              + "\nAlready found in\n  '{}'\nwith value '{}' (active).",
266         new Object[] { name, newValue, newSource, currentSource, currentValue });
267   }
268 
269   private void warnDuplicateWithSameValue(final String name,
270       final PropertyLocation currentSource, final PropertyLocation newSource,
271       final Object commonValue)
272   {
273     LOG.warn("Duplicate key '{}' with same value '{}' in\n   '{}'\n and in\n"
274              + "   '{}'.", new Object[] { name, commonValue, currentSource,
275                                          newSource });
276   }
277 
278   Property getValue(final String key)
279   {
280     return getValue(key, false);
281   }
282 
283   Property getValue(final String key, final boolean excludeLazies)
284   {
285     final List<Property> properties = new LinkedList<Property>();
286     for (final Map.Entry<PropertyLocation, PropertyProvider> entry : propertiesMap
287         .entrySet())
288     {
289       final PropertyProvider currentProperties = entry.getValue();
290 
291       if(excludeLazies && currentProperties.isLazy())
292       {
293         continue;
294       }
295 
296       if (currentProperties.containsKey(key))
297       {
298         final Property property = currentProperties.getProperty(key);
299         properties.add(property);
300       }
301     }
302 
303     if (properties.isEmpty())
304     {
305       return null;
306     }
307     else if (properties.size() == 1)
308     {
309       return properties.get(0);
310     }
311     else
312     {
313       LOG.warn("Duplicate key '{}' in\n  {}.", new Object[] { key, properties });
314       return properties.get(0);
315     }
316   }
317   // --- object basics --------------------------------------------------------
318 
319   /**
320    * Returns the string representation of the object.
321    *
322    * @return the string representation of the object.
323    */
324   @Override
325   public String toString()
326   {
327     final StringBuilder buffer = new StringBuilder();
328 
329     buffer.append(configurationKey).append('=');
330 
331     for (final PropertyLocation source : propertiesMap.keySet())
332     {
333       buffer.append(' ').append(source);
334     }
335 
336     if (exceptions != null)
337     {
338       buffer.append("Exceptions:");
339       for (final Exception e : exceptions)
340       {
341         buffer.append('\n').append(e.getMessage());
342       }
343     }
344 
345     return buffer.toString();
346   }
347 }