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