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.List;
19  import java.util.Map.Entry;
20  import java.util.Properties;
21  
22  import javax.annotation.concurrent.ThreadSafe;
23  
24  import org.apache.commons.lang.ObjectUtils;
25  import org.apache.commons.lang.StringUtils;
26  
27  import de.smartics.properties.api.config.domain.ConfigurationPropertiesManagement;
28  import de.smartics.properties.api.config.domain.ConfigurationValidationException;
29  import de.smartics.properties.api.config.domain.Property;
30  import de.smartics.properties.api.config.domain.PropertyCollection;
31  import de.smartics.properties.api.config.domain.PropertyLocation;
32  import de.smartics.properties.api.config.domain.PropertyValidationWithSourceException;
33  import de.smartics.properties.api.config.domain.PropertyValueConversionWithSourceException;
34  import de.smartics.properties.api.config.domain.PropertyValueResolveWithSourceException;
35  import de.smartics.properties.api.config.domain.ResolvedProperty;
36  import de.smartics.properties.api.config.domain.UnknownPropertyException;
37  import de.smartics.properties.api.config.domain.key.ConfigurationKey;
38  import de.smartics.properties.api.core.domain.DuplicatePropertyDeclarationsException;
39  import de.smartics.properties.api.core.domain.PropertyDescriptor;
40  import de.smartics.properties.api.core.domain.PropertyDescriptorRegistry;
41  import de.smartics.properties.api.core.domain.PropertyExpression;
42  import de.smartics.properties.api.core.domain.PropertyKey;
43  import de.smartics.properties.api.core.domain.PropertyValidationException;
44  import de.smartics.properties.api.core.domain.PropertyValueConversionException;
45  import de.smartics.properties.api.core.domain.PropertyValueResolveException;
46  import de.smartics.properties.api.core.domain.ReadOnlyPropertyException;
47  import de.smartics.properties.spi.config.validation.ConfigurationValidator;
48  import de.smartics.util.lang.Arguments;
49  import de.smartics.util.lang.NullArgumentException;
50  
51  /**
52   * Abstract implementation of the
53   * {@link de.smartics.properties.api.config.domain.ConfigurationProperties}
54   * interface.
55   */
56  @ThreadSafe
57  public abstract class AbstractConfigurationPropertiesManagement extends
58      AbstractConfigurationProperties implements
59      ConfigurationPropertiesManagement, ConfigurationPropertiesManagementSpi
60  { // NOPMD
61    // ********************************* Fields *********************************
62  
63    // --- constants ------------------------------------------------------------
64  
65    // --- members --------------------------------------------------------------
66  
67    // ****************************** Initializer *******************************
68  
69    // ****************************** Constructors ******************************
70  
71    /**
72     * Constructor for serializable subclasses.
73     */
74    protected AbstractConfigurationPropertiesManagement()
75    {
76      super();
77    }
78  
79    /**
80     * Default constructor.
81     *
82     * @param key the key that identifies the configuration.
83     * @param registry the registry to resolve property descriptors.
84     * @throws NullArgumentException if {@code key} or {@code registry} is
85     *           <code>null</code>.
86     */
87    protected AbstractConfigurationPropertiesManagement(
88        final ConfigurationKey key, final PropertyDescriptorRegistry registry)
89      throws NullArgumentException
90    {
91      super(key, registry);
92    }
93  
94    // ****************************** Inner Classes *****************************
95  
96    // ********************************* Methods ********************************
97  
98    // --- init -----------------------------------------------------------------
99  
100   // --- get&set --------------------------------------------------------------
101 
102   // --- business -------------------------------------------------------------
103 
104   // ... descriptor handling ..................................................
105 
106   @Override
107   public final PropertyDescriptor getDescriptor(final String key)
108     throws UnknownPropertyException
109   {
110     final PropertyDescriptor descriptor = getRegistry().get(key);
111 
112     if (descriptor == null)
113     {
114       throw new UnknownPropertyException(getKey(), key);
115     }
116 
117     return descriptor;
118   }
119 
120   @Override
121   public final PropertyDescriptor getDescriptor(final PropertyKey key)
122     throws UnknownPropertyException
123   {
124     return getDescriptor(key.toString());
125   }
126 
127   @Override
128   public final void addDescriptors(final Class<?> propertySetType)
129     throws DuplicatePropertyDeclarationsException
130   {
131     getRegistry().addDescriptors(propertySetType);
132   }
133 
134   @Override
135   public final List<PropertyDescriptor> getMandatoryPropertyDescriptors()
136   {
137     return getRegistry().createMandatoryProperties();
138   }
139 
140   @Override
141   public final ConfigurationPropertiesManagement addDefinitions(
142       final Properties properties) throws NullPointerException
143   {
144     for (final Entry<Object, Object> entry : properties.entrySet())
145     {
146       final String name = ObjectUtils.toString(entry.getKey(), null);
147       if (StringUtils.isNotBlank(name))
148       {
149         final String stringValue = unwrapValue(entry.getValue());
150         setPropertyToStore(name, stringValue);
151       }
152     }
153     return this;
154   }
155 
156   // ... managing properties ..................................................
157 
158   @Override
159   public final Property getProperty(final String key, final Object defaultValue)
160     throws IllegalArgumentException, UnknownPropertyException
161   {
162     final PropertyDescriptor descriptor = getDescriptor(key);
163     return getProperty(descriptor, defaultValue);
164   }
165 
166   @Override
167   public final Property getProperty(final PropertyDescriptor descriptor,
168       final Object defaultValue) throws IllegalArgumentException,
169     UnknownPropertyException
170   {
171     final String name = descriptor.getKey().toString();
172     final Property property = getPropertyFromStore(name);
173     final Object plainValue = property.getValue();
174 
175     if (plainValue == null && defaultValue == null && descriptor.isMandatory())
176     {
177       // We do not required that properties with a value of 'null' are specified
178       // explicitly. Therefore we must signal an unknown property only if it is
179       // mandatory.
180       // TODO: Check if we want to be here more strict. In this case every
181       // property has to be registered with a "null" value, if it is not
182       // explicitly provided.
183 
184       throw new UnknownPropertyException(getKey(), name);
185     }
186 
187     return property;
188   }
189 
190   @Override
191   public final ResolvedProperty getResolvedProperty(
192       final PropertyDescriptor descriptor, final Object defaultValue)
193     throws IllegalArgumentException, UnknownPropertyException,
194     PropertyValidationException
195   {
196     final Property property = getProperty(descriptor, defaultValue);
197 
198     final Object plainValue = property.getValue();
199     try
200     {
201       final Object resolvedValue =
202           resolveAndConvert(descriptor, defaultValue, plainValue);
203       return new ResolvedProperty(property, getExpression(descriptor),
204           resolvedValue);
205     }
206     catch (final PropertyValueResolveException e)
207     {
208       throw wrapWithPropertyValueSourceInformation(e, descriptor, property);
209     }
210     catch (final PropertyValueConversionException e)
211     {
212       throw wrapWithPropertyValueSourceInformation(e, property);
213     }
214     catch (final PropertyValidationException e)
215     {
216       throw wrapWithPropertyValueSourceInformation(e, property);
217     }
218   }
219 
220   private static String getExpression(final PropertyDescriptor descriptor)
221   {
222     final PropertyExpression expression = descriptor.getDefaultExpression();
223     return expression != null ? expression.getExpression() : null;
224   }
225 
226   @Override
227   public final Property setProperty(final PropertyKey key, final String value)
228     throws NullPointerException, PropertyValidationException,
229     ReadOnlyPropertyException
230   {
231     Arguments.checkNotNull("key", key);
232     checkWritable(key, value);
233 
234     final String name = key.toString();
235     return setPropertyAndFireEvent(name, value);
236   }
237 
238   private Property setPropertyAndFireEvent(final String name, final String value)
239     throws NullPointerException, PropertyValidationException,
240     ReadOnlyPropertyException
241   {
242     final Property oldProperty = setPropertyToStore(name, value);
243     firePropertyChange(name, oldProperty != null ? oldProperty.getValue()
244         : null, value);
245     return oldProperty;
246   }
247 
248   @Override
249   public final Property unsetProperty(final PropertyKey key)
250     throws NullPointerException, ReadOnlyPropertyException
251   {
252     Arguments.checkNotNull("key", key);
253     checkWritable(key, null);
254 
255     final String name = key.toString();
256     final Property oldProperty = deletePropertyInStore(name);
257     firePropertyChange(name, oldProperty.getValue(), null);
258     return oldProperty;
259   }
260 
261   @Override
262   public final void validate(final boolean lenient)
263     throws ConfigurationValidationException
264   {
265     final ConfigurationValidator validator =
266         new ConfigurationValidator(this, lenient);
267     final PropertyCollection propertyCollection =
268         getPropertyCollectionFromStore();
269     validator.validate(propertyCollection);
270   }
271 
272   // ... configuration management .............................................
273 
274   /**
275    * {@inheritDoc}
276    * <p>
277    * This implementation does nothing on a flush. Should be overridden by
278    * implementations that do want to take actions on a flush.
279    * </p>
280    */
281   @Override
282   public void flush()
283   {
284   }
285 
286   // ... property store access ................................................
287 
288   /**
289    * Sets the property to the given value.
290    *
291    * @param name the name of the property to set.
292    * @param value the value to the property.
293    * @return the old property. Must not be <code>null</code> (although the value
294    *         of the property may be <code>null</code>).
295    * @throws NullPointerException if {@code name} is <code>null</code>.
296    * @impl No property change listeners are informed here. This is solely the
297    *       call to the underlying store.
298    */
299   protected abstract Property setPropertyToStore(final String name,
300       final String value) throws NullPointerException;
301 
302   /**
303    * Returns a collection to iterate over all properties of the configuration.
304    *
305    * @return a collection to iterate over all properties of the configuration.
306    * @impl No property change listeners are informed here. This is solely the
307    *       call to the underlying store.
308    */
309   protected abstract PropertyCollection getPropertyCollectionFromStore();
310 
311   /**
312    * Deletes the property with the given name.
313    *
314    * @param name the name of the property to delete.
315    * @return the value of the deleted property.
316    * @impl No property change listeners are informed here. This is solely the
317    *       call to the underlying store.
318    */
319   protected abstract Property deletePropertyInStore(final String name);
320 
321   /**
322    * Fetches the property from the store by the given name.
323    *
324    * @param name the name of the property to fetch.
325    * @return the property value. Must not be <code>null</code> (although the
326    *         value of the property may be <code>null</code>).
327    * @impl No property change listeners are informed here. This is solely the
328    *       call to the underlying store.
329    */
330   protected abstract Property getPropertyFromStore(final String name);
331 
332   // ... helpers ..............................................................
333 
334   /**
335    * Checks if the property identified by the given key is writable.
336    *
337    * @param key the key to identify the property.
338    * @param value the new value (only used in case of an exception).
339    * @throws ReadOnlyPropertyException if the property is read-only.
340    * @throws UnknownPropertyException if the property referenced by {@code key}
341    *           is not known.
342    */
343   private void checkWritable(final PropertyKey key, final Object value)
344     throws ReadOnlyPropertyException, UnknownPropertyException
345   {
346     final PropertyDescriptor descriptor = getDescriptor(key);
347 
348     if (!descriptor.isRuntimeMutable())
349     {
350       final String currentValue = getPropertyValueAsString(descriptor);
351       throw new ReadOnlyPropertyException(descriptor, currentValue, value);
352     }
353   }
354 
355   /**
356    * Checks if the given {@code value} is an instance of {@link Property} and
357    * unwraps the {@code value} if it is.
358    *
359    * @param value the value to check and unwrap.
360    * @return the {@code value} or the unwrapped {@code value} dependent on the
361    *         type of the {@code value}.
362    */
363   private String unwrapValue(final Object value)
364   {
365     if (value instanceof Property)
366     {
367       return ((Property) value).getValue();
368     }
369     return ObjectUtils.toString(value, null);
370   }
371 
372   private static PropertyValueResolveException wrapWithPropertyValueSourceInformation(
373       final PropertyValueResolveException e,
374       final PropertyDescriptor descriptor, final Property property)
375   {
376     final PropertyValueResolveException cause =
377         isResolvementFailureOfPlaceholder(e, property)
378             ? new PropertyValueResolveException(e, descriptor,
379                 property.getValue()) : e;
380 
381     if (!(cause instanceof PropertyValueResolveWithSourceException))
382     {
383       final PropertyLocation source = property.getSource();
384       return new PropertyValueResolveWithSourceException(cause, source);
385     }
386 
387     return e;
388   }
389 
390   private static boolean isResolvementFailureOfPlaceholder(
391       final PropertyValueResolveException e, final Property property)
392   {
393     return !property.getName().equals(e.getPropertyKey().getName());
394   }
395 
396   private static PropertyValueConversionException wrapWithPropertyValueSourceInformation(
397       final PropertyValueConversionException e, final Property property)
398   {
399     final PropertyLocation source = property.getSource();
400     return new PropertyValueConversionWithSourceException(e, source);
401   }
402 
403   private static PropertyValidationException wrapWithPropertyValueSourceInformation(
404       final PropertyValidationException e, final Property property)
405   {
406     final PropertyLocation source = property.getSource();
407     return new PropertyValidationWithSourceException(e, source);
408   }
409 
410   // --- object basics --------------------------------------------------------
411 
412 }