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  
20  import javax.annotation.concurrent.ThreadSafe;
21  
22  import de.smartics.properties.api.config.domain.ConfigurationPropertiesManagement;
23  import de.smartics.properties.api.config.domain.ConfigurationValidationException;
24  import de.smartics.properties.api.config.domain.DescribedProperty;
25  import de.smartics.properties.api.config.domain.Property;
26  import de.smartics.properties.api.config.domain.PropertyCollection;
27  import de.smartics.properties.api.config.domain.PropertyExpressionWithSourceMessageBean;
28  import de.smartics.properties.api.config.domain.PropertyLocation;
29  import de.smartics.properties.api.config.domain.PropertyProvider;
30  import de.smartics.properties.api.config.domain.PropertyStoreException;
31  import de.smartics.properties.api.config.domain.PropertyValidationWithSourceException;
32  import de.smartics.properties.api.config.domain.PropertyValidationWithSourceMessageBean;
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.PropertyValueWithSourceMessageBean;
36  import de.smartics.properties.api.config.domain.UnknownPropertyException;
37  import de.smartics.properties.api.config.domain.ValidatedProperty;
38  import de.smartics.properties.api.config.domain.key.ConfigurationKey;
39  import de.smartics.properties.api.core.app.PropertyRootException;
40  import de.smartics.properties.api.core.domain.DuplicatePropertyDeclarationsException;
41  import de.smartics.properties.api.core.domain.PropertyCode;
42  import de.smartics.properties.api.core.domain.PropertyDescriptor;
43  import de.smartics.properties.api.core.domain.PropertyDescriptorRegistry;
44  import de.smartics.properties.api.core.domain.PropertyExpression;
45  import de.smartics.properties.api.core.domain.PropertyExpressionMessageBean;
46  import de.smartics.properties.api.core.domain.PropertyKey;
47  import de.smartics.properties.api.core.domain.PropertyValidationException;
48  import de.smartics.properties.api.core.domain.PropertyValidationMessageBean;
49  import de.smartics.properties.api.core.domain.PropertyValueChangeMessageBean;
50  import de.smartics.properties.api.core.domain.PropertyValueConversionException;
51  import de.smartics.properties.api.core.domain.PropertyValueResolveException;
52  import de.smartics.properties.api.core.domain.ReadOnlyPropertyException;
53  import de.smartics.properties.api.core.security.PropertyValueSecurity;
54  import de.smartics.properties.api.core.security.SecurityException;
55  import de.smartics.properties.spi.config.validation.ConfigurationValidator;
56  import de.smartics.util.lang.Arg;
57  import de.smartics.util.lang.NullArgumentException;
58  
59  /**
60   * Abstract implementation of the
61   * {@link de.smartics.properties.api.config.domain.ConfigurationProperties}
62   * interface.
63   */
64  @ThreadSafe
65  public abstract class AbstractConfigurationPropertiesManagement extends
66      AbstractAdminModeConfigurationProperties implements
67      ConfigurationPropertiesManagement, ConfigurationPropertiesManagementSpi
68  { // NOPMD
69    // ********************************* Fields *********************************
70  
71    // --- constants ------------------------------------------------------------
72  
73    // --- members --------------------------------------------------------------
74  
75    /**
76     * The properties associated to override.
77     *
78     * @serial
79     */
80    private final MultiSourceProperties properties;
81  
82    // ****************************** Initializer *******************************
83  
84    // ****************************** Constructors ******************************
85  
86    /**
87     * Constructor for serializable subclasses.
88     */
89    protected AbstractConfigurationPropertiesManagement()
90    {
91      super();
92      this.properties = null;
93    }
94  
95    /**
96     * Default constructor.
97     *
98     * @param key the key that identifies the configuration.
99     * @param registry the registry to resolve property descriptors.
100    * @param decrypter the helper to decrypt secured property values.
101    * @throws NullArgumentException if {@code key}, {@code registry} or
102    *           {@code decrypter} is <code>null</code>.
103    */
104   protected AbstractConfigurationPropertiesManagement(
105       final ConfigurationKey<?> key, final PropertyDescriptorRegistry registry,
106       final PropertyValueSecurity decrypter) throws NullArgumentException
107   {
108     super(key, registry, decrypter);
109 
110     this.properties = new MultiSourceProperties(key);
111   }
112 
113   // ****************************** Inner Classes *****************************
114 
115   // ********************************* Methods ********************************
116 
117   // --- init -----------------------------------------------------------------
118 
119   // --- get&set --------------------------------------------------------------
120 
121   // --- business -------------------------------------------------------------
122 
123   // ... descriptor handling ..................................................
124 
125   @Override
126   public final PropertyDescriptor getDescriptor(final String key)
127     throws UnknownPropertyException
128   {
129     final PropertyDescriptor descriptor = getRegistry().get(key);
130 
131     if (descriptor == null)
132     {
133       throw new UnknownPropertyException(getKey(), key);
134     }
135 
136     return descriptor;
137   }
138 
139   @Override
140   public final PropertyDescriptor getDescriptor(final PropertyKey key)
141     throws UnknownPropertyException
142   {
143     return getDescriptor(key.toString());
144   }
145 
146   @Override
147   public final void addDescriptors(final Class<?> propertySetType)
148     throws DuplicatePropertyDeclarationsException
149   {
150     getRegistry().addDescriptors(propertySetType);
151   }
152 
153   @Override
154   public final List<PropertyDescriptor> getMandatoryPropertyDescriptors()
155   {
156     return getRegistry().createMandatoryProperties();
157   }
158 
159   @Override
160   public final ConfigurationPropertiesManagement addDefinitions(
161       final PropertyProvider properties) throws NullPointerException
162   {
163     this.properties.addProvider(properties);
164     addDefinitionsToStore(properties);
165     return this;
166   }
167 
168   /**
169    * Implementations decide whether or not registered definitions should be
170    * written to the store. This is the default and subclasses may override to
171    * implement another strategy.
172    * <p>
173    * Usually configurations that are backed up by an external (e.g. persistent)
174    * store do not want to store definitions here. So they override this method
175    * with a no-op.
176    * </p>
177    *
178    * @param properties the properties that are to be registered as definitions.
179    * @throws PropertyStoreException if any property cannot be stored. The
180    *           already stored properties will not be rolled back.
181    */
182   protected abstract void addDefinitionsToStore(
183       final PropertyProvider properties) throws PropertyStoreException;
184 
185   /**
186    * Allows to store all properties efficiently by subclasses. The default
187    * implementation simply iterates.
188    *
189    * @param provider the provider with the properties to be stored.
190    * @throws PropertyStoreException if any property cannot be stored. The
191    *           already stored properties will not be rolled back.
192    */
193   protected abstract void setPropertiesToStore(final PropertyProvider provider)
194     throws PropertyStoreException;
195 
196   // ... managing properties ..................................................
197 
198   @Override
199   public final DescribedProperty getProperty(final String key,
200       final Object defaultValue) throws IllegalArgumentException,
201     UnknownPropertyException
202   {
203     final PropertyDescriptor descriptor = getDescriptor(key);
204     return getProperty(descriptor, defaultValue);
205   }
206 
207   @Override
208   public final DescribedProperty getProperty(
209       final PropertyDescriptor descriptor, final Object defaultValue)
210     throws IllegalArgumentException, UnknownPropertyException
211   {
212     final DescribedProperty property = getPropertyInternal(descriptor);
213 
214     return property;
215   }
216 
217   private DescribedProperty getPropertyInternal(
218       final PropertyDescriptor descriptor)
219   {
220     final String name = descriptor.getKey().toString();
221     final Property property = getPropertyFromStore(name);
222     // if (property == null || property.getValue() == null)
223     // { // if alternative lookups have to be provided, the 'properties' have to
224     // be updated on every change.
225     // final Property alt = properties.getValue(name);
226     // property = alt != null ? alt : property;
227     // }
228 
229     final DescribedProperty describedProperty =
230         new DescribedProperty(this.getKey(), descriptor, property);
231 
232     return describedProperty;
233   }
234 
235   @Override
236   public Object getPropertyAsType(final PropertyDescriptor descriptor)
237     throws IllegalArgumentException, PropertyValueConversionException,
238     SecurityException, PropertyRootException
239   {
240     final Property property = getProperty(descriptor);
241     final String plainValue = property.getValue();
242     final Object resolvedValue =
243         resolveAndConvert(descriptor, plainValue, null);
244     return resolvedValue;
245   }
246 
247   @Override
248   public final ValidatedProperty getValidatedProperty(
249       final PropertyDescriptor descriptor, final Object defaultValue)
250     throws IllegalArgumentException, UnknownPropertyException,
251     PropertyValidationException, SecurityException
252   {
253     final Property property = getProperty(descriptor, defaultValue);
254 
255     final Object plainValue = property.getValue();
256     if (plainValue == null && defaultValue == null && descriptor.isMandatory())
257     {
258       // We do not required that properties with a value of 'null' are specified
259       // explicitly. Therefore we must signal an unknown property only if it is
260       // mandatory.
261 
262       final PropertyValidationMessageBean message =
263           new PropertyValidationMessageBean(descriptor,
264               descriptor.getConstraints(), plainValue);
265       throw new PropertyValidationException(message);
266     }
267 
268     try
269     {
270       final Object resolvedValue =
271           resolveAndConvertAndValidate(descriptor, defaultValue, plainValue);
272       final DescribedProperty describedProperty =
273           new DescribedProperty(getKey(), descriptor, property);
274       return new ValidatedProperty(describedProperty,
275           getExpression(descriptor), resolvedValue);
276     }
277     catch (final PropertyValueResolveException e)
278     {
279       throw wrapWithPropertyValueSourceInformation(e, descriptor, property);
280     }
281     catch (final PropertyValueConversionException e)
282     {
283       throw wrapWithPropertyValueSourceInformation(e, property);
284     }
285     catch (final PropertyValidationException e)
286     {
287       throw wrapWithPropertyValueSourceInformation(e, property);
288     }
289   }
290 
291   private static String getExpression(final PropertyDescriptor descriptor)
292   {
293     final PropertyExpression expression = descriptor.getDefaultExpression();
294     return expression != null ? expression.getExpression() : null;
295   }
296 
297   @Override
298   public final Property setProperty(final PropertyKey key, final String value)
299     throws NullPointerException, PropertyValidationException,
300     ReadOnlyPropertyException
301   {
302     Arg.checkNotNull("key", key);
303     checkWritable(key, value);
304 
305     final String name = key.toString();
306     return setPropertyAndFireEvent(name, value);
307   }
308 
309   private Property setPropertyAndFireEvent(final String name, final String value)
310     throws NullPointerException, PropertyValidationException,
311     ReadOnlyPropertyException
312   {
313     final String normalized = provideSecurity(name, value);
314     final Property oldProperty = setPropertyToStore(name, normalized);
315     firePropertyChange(name, oldProperty != null ? oldProperty.getValue()
316         : null, normalized);
317     return oldProperty;
318   }
319 
320   private String provideSecurity(final String name, final String value)
321   {
322     if (value != null)
323     {
324       final PropertyDescriptor descriptor = getRegistry().get(name);
325       if (descriptor.isSecured())
326       {
327         final String encryptedValue =
328             getPropertyValueSecurity().encrypt(descriptor, value);
329         return encryptedValue;
330       }
331     }
332 
333     return value;
334   }
335 
336   @Override
337   public final Property unsetProperty(final PropertyKey key)
338     throws NullPointerException, ReadOnlyPropertyException
339   {
340     Arg.checkNotNull("key", key);
341     checkWritable(key, null);
342 
343     final String name = key.toString();
344     final Property oldProperty = deletePropertyInStore(name);
345     firePropertyChange(name, oldProperty.getValue(), null);
346     return oldProperty;
347   }
348 
349   @Override
350   public final void validate(final boolean lenient, final Class<?>... groups)
351     throws ConfigurationValidationException
352   {
353     final ConfigurationValidator validator =
354         new ConfigurationValidator(this, lenient);
355     final PropertyCollection propertyCollection =
356         getPropertyCollectionFromStore();
357     validator.validate(propertyCollection, groups);
358   }
359 
360   @Override
361   public final void validate(final PropertyDescriptor descriptor,
362       final Class<?>... ifInOneOfTheseGroups)
363     throws ConfigurationValidationException
364   {
365     final Object value = getPropertyValue(descriptor);
366     validate(descriptor, value, ifInOneOfTheseGroups);
367   }
368 
369   @Override
370   public final void validate(final PropertyDescriptor descriptor,
371       final String value, final Class<?>... ifInOneOfTheseGroups)
372     throws ConfigurationValidationException
373   {
374     final ConfigurationValidator validator =
375         new ConfigurationValidator(this, false);
376     final Object valueAsType = resolveAndConvert(descriptor, value, null);
377 
378     validator.validate(descriptor, valueAsType, ifInOneOfTheseGroups);
379   }
380 
381   // ... configuration management .............................................
382 
383   /**
384    * {@inheritDoc}
385    * <p>
386    * This implementation does nothing on a flush. Should be overridden by
387    * implementations that do want to take actions on a flush.
388    * </p>
389    */
390   @Override
391   public void flush()
392   {
393   }
394 
395   // ... property store access ................................................
396 
397   /**
398    * Sets the property to the given value.
399    *
400    * @param name the name of the property to set.
401    * @param value the value to the property.
402    * @return the old property. Must not be <code>null</code> (although the value
403    *         of the property may be <code>null</code>).
404    * @throws NullPointerException if {@code name} is <code>null</code>.
405    * @impl No property change listeners are informed here. This is solely the
406    *       call to the underlying store.
407    */
408   protected final Property setPropertyToStore(final String name,
409       final String value) throws NullPointerException
410   {
411     return getPropertyStoreAccessor().setPropertyToStore(name, value);
412   }
413 
414   /**
415    * Returns a collection to iterate over all properties of the configuration.
416    *
417    * @return a collection to iterate over all properties of the configuration.
418    * @impl No property change listeners are informed here. This is solely the
419    *       call to the underlying store.
420    */
421   protected final PropertyCollection getPropertyCollectionFromStore()
422   {
423     return getPropertyStoreAccessor().getPropertyCollectionFromStore();
424   }
425 
426   /**
427    * Deletes the property with the given name.
428    *
429    * @param name the name of the property to delete.
430    * @return the value of the deleted property.
431    * @impl No property change listeners are informed here. This is solely the
432    *       call to the underlying store.
433    */
434   protected final Property deletePropertyInStore(final String name)
435   {
436     return getPropertyStoreAccessor().deletePropertyInStore(name);
437   }
438 
439   /**
440    * Fetches the property from the store by the given name.
441    *
442    * @param name the name of the property to fetch.
443    * @return the property value. Must not be <code>null</code> (although the
444    *         value of the property may be <code>null</code>).
445    * @impl No property change listeners are informed here. This is solely the
446    *       call to the underlying store.
447    */
448   protected final Property getPropertyFromStore(final String name)
449   {
450     return getPropertyStoreAccessor().getPropertyFromStore(name);
451   }
452 
453   // ... helpers ..............................................................
454 
455   /**
456    * Checks if the property identified by the given key is writable.
457    *
458    * @param key the key to identify the property.
459    * @param value the new value (only used in case of an exception).
460    * @throws ReadOnlyPropertyException if the property is read-only.
461    * @throws UnknownPropertyException if the property referenced by {@code key}
462    *           is not known.
463    */
464   private void checkWritable(final PropertyKey key, final Object value)
465     throws ReadOnlyPropertyException, UnknownPropertyException
466   {
467     if (isInAdminMode())
468     {
469       return;
470     }
471 
472     final PropertyDescriptor descriptor = getDescriptor(key);
473 
474     if (!descriptor.isRuntimeMutable())
475     {
476       final Object currentValue = getPropertyAsType(descriptor);
477       throw new ReadOnlyPropertyException(new PropertyValueChangeMessageBean(
478           PropertyCode.READ_ONLY, descriptor, currentValue, value));
479     }
480   }
481 
482   private static PropertyValueResolveException wrapWithPropertyValueSourceInformation(
483       final PropertyValueResolveException e,
484       final PropertyDescriptor descriptor, final Property property)
485   {
486     final PropertyValueResolveException cause =
487         isResolvementFailureOfPlaceholder(e, property)
488             ? new PropertyValueResolveException(
489                 new PropertyExpressionMessageBean(e, descriptor,
490                     property.getValue())) : e;
491 
492     if (!(cause instanceof PropertyValueResolveWithSourceException))
493     {
494       final PropertyLocation source = property.getSource();
495       return new PropertyValueResolveWithSourceException(
496           new PropertyExpressionWithSourceMessageBean(cause, source));
497     }
498 
499     return e;
500   }
501 
502   private static boolean isResolvementFailureOfPlaceholder(
503       final PropertyValueResolveException e, final Property property)
504   {
505     return !property.getName().equals(e.getPropertyKey().getName());
506   }
507 
508   private static PropertyValueConversionException wrapWithPropertyValueSourceInformation(
509       final PropertyValueConversionException e, final Property property)
510   {
511     final PropertyLocation source = property.getSource();
512     return new PropertyValueConversionWithSourceException(
513         new PropertyValueWithSourceMessageBean(e, source));
514   }
515 
516   private static PropertyValidationException wrapWithPropertyValueSourceInformation(
517       final PropertyValidationException e, final Property property)
518   {
519     final PropertyLocation source = property.getSource();
520     return new PropertyValidationWithSourceException(
521         new PropertyValidationWithSourceMessageBean(e, source));
522   }
523 
524   // --- object basics --------------------------------------------------------
525 
526 }