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.ArrayList;
19  import java.util.List;
20  
21  import de.smartics.properties.api.config.domain.ConfigurationPropertiesManagement;
22  import de.smartics.properties.api.config.domain.ConfigurationValidationException;
23  import de.smartics.properties.api.config.domain.DescribedProperty;
24  import de.smartics.properties.api.config.domain.Property;
25  import de.smartics.properties.api.config.domain.PropertyCollection;
26  import de.smartics.properties.api.config.domain.PropertyExpressionWithSourceMessageBean;
27  import de.smartics.properties.api.config.domain.PropertyLocation;
28  import de.smartics.properties.api.config.domain.PropertyProvider;
29  import de.smartics.properties.api.config.domain.PropertyStoreAccessor;
30  import de.smartics.properties.api.config.domain.PropertyStoreCode;
31  import de.smartics.properties.api.config.domain.PropertyStoreException;
32  import de.smartics.properties.api.config.domain.PropertyStoreMessageBean;
33  import de.smartics.properties.api.config.domain.PropertyValidationWithSourceException;
34  import de.smartics.properties.api.config.domain.PropertyValidationWithSourceMessageBean;
35  import de.smartics.properties.api.config.domain.PropertyValueConversionWithSourceException;
36  import de.smartics.properties.api.config.domain.PropertyValueResolveWithSourceException;
37  import de.smartics.properties.api.config.domain.PropertyValueWithSourceMessageBean;
38  import de.smartics.properties.api.config.domain.SerializableConfigurationPropertiesManagement;
39  import de.smartics.properties.api.config.domain.UnknownPropertyException;
40  import de.smartics.properties.api.config.domain.ValidatedProperty;
41  import de.smartics.properties.api.config.domain.key.ConfigurationKey;
42  import de.smartics.properties.api.core.app.PropertyRootException;
43  import de.smartics.properties.api.core.domain.DuplicatePropertyDeclarationsException;
44  import de.smartics.properties.api.core.domain.PropertyDescriptor;
45  import de.smartics.properties.api.core.domain.PropertyExpression;
46  import de.smartics.properties.api.core.domain.PropertyExpressionMessageBean;
47  import de.smartics.properties.api.core.domain.PropertyKey;
48  import de.smartics.properties.api.core.domain.PropertyValidationException;
49  import de.smartics.properties.api.core.domain.PropertyValidationMessageBean;
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.SecurityException;
54  import de.smartics.properties.spi.config.validation.ConfigurationValidator;
55  
56  /**
57   * A configuration properties instance that contains the main configuration
58   * properties, which matches the key, and all the defaults that have a partial
59   * representation of the key.
60   * <p>
61   * This implementation is thread-safe as long as the passed in instances of
62   * {@link ConfigurationPropertiesManagement} are thread-safe.
63   * </p>
64   */
65  public final class ConfigurationPropertiesManagementWithDefaults extends
66      AbstractConfigurationProperties implements
67      SerializableConfigurationPropertiesManagement
68  {
69    // ********************************* Fields *********************************
70  
71    // --- constants ------------------------------------------------------------
72  
73    // --- members --------------------------------------------------------------
74  
75    /**
76     * The class version identifier.
77     */
78    private static final long serialVersionUID = 1L;
79  
80    /**
81     * The list of configuration properties to consult on requests.
82     *
83     * @serial
84     */
85    private final List<SerializableConfigurationPropertiesManagement> configurationProperties;
86  
87    /**
88     * The helper to access properties in a property store directly.
89     *
90     * @serial
91     */
92    private final PropertyStoreAccessor propertyStoreAccessor =
93        new MultiPropertyStoreAccessor();
94  
95    // ****************************** Initializer *******************************
96  
97    // ****************************** Constructors ******************************
98  
99    /**
100    * Default constructor.
101    *
102    * @param configurationProperties the list of configuration properties to
103    *          consult on requests.
104    * @throws IllegalArgumentException if {@code configurationProperties} is
105    *           <code>null</code> or empty.
106    */
107   public ConfigurationPropertiesManagementWithDefaults(
108       final List<ConfigurationPropertiesManagement> configurationProperties)
109     throws IllegalArgumentException
110   {
111     super(getConfigurationKey(configurationProperties), configurationProperties
112         .get(0).getRegistry(), configurationProperties.get(0)
113         .getPropertyValueSecurity());
114 
115     this.configurationProperties = toSerializable(configurationProperties);
116   }
117 
118   // ****************************** Inner Classes *****************************
119 
120   /**
121    * Provides direct access to the properties in the property store.
122    */
123   private final class MultiPropertyStoreAccessor implements
124       PropertyStoreAccessor
125   {
126     /**
127      * The class version identifier.
128      * <p>
129      * The value of this constant is {@value}.
130      * </p>
131      */
132     private static final long serialVersionUID = 1L;
133 
134     @Override
135     public Property getPropertyFromStore(final String name)
136       throws PropertyStoreException
137     {
138       for (final ConfigurationPropertiesManagement instance : configurationProperties)
139       {
140         final PropertyStoreAccessor accessor =
141             instance.getPropertyStoreAccessor();
142         final Property property = accessor.getPropertyFromStore(name);
143         if (property.getValue() != null)
144         {
145           return property;
146         }
147       }
148 
149       throw new PropertyStoreException(
150           new PropertyStoreMessageBean(
151               PropertyStoreCode.CANNOT_GET_PROPERTY,
152               new IllegalStateException(
153                   "The list of configuration properties is empty."
154                       + " It is required that there is at least one element in it."),
155               getKey(), name));
156     }
157 
158     @Override
159     public Property setPropertyToStore(final String name, final String value)
160       throws PropertyValidationException, ReadOnlyPropertyException
161     {
162       return representative().getPropertyStoreAccessor().setPropertyToStore(
163           name, value);
164     }
165 
166     @Override
167     public Property deletePropertyInStore(final String name)
168     {
169       return representative().getPropertyStoreAccessor().deletePropertyInStore(
170           name);
171     }
172 
173     @Override
174     public PropertyCollection getPropertyCollectionFromStore()
175     {
176       final MultiPropertyCollection collections = new MultiPropertyCollection();
177       for (final ConfigurationPropertiesManagement instance : configurationProperties)
178       {
179         final PropertyStoreAccessor accessor =
180             instance.getPropertyStoreAccessor();
181 
182         final PropertyCollection collection =
183             accessor.getPropertyCollectionFromStore();
184         collections.add(collection);
185       }
186 
187       return collections;
188     }
189   }
190 
191   // ********************************* Methods ********************************
192 
193   // --- init -----------------------------------------------------------------
194 
195   private static ConfigurationKey<?> getConfigurationKey(
196       final List<ConfigurationPropertiesManagement> configurationProperties)
197   {
198     if (configurationProperties == null || configurationProperties.isEmpty())
199     {
200       throw new IllegalArgumentException(
201           "The configurationProperties must not be empty.");
202     }
203     return configurationProperties.get(0).getKey();
204   }
205 
206   private static List<SerializableConfigurationPropertiesManagement> toSerializable(
207       final List<ConfigurationPropertiesManagement> configurationProperties)
208   {
209     final List<SerializableConfigurationPropertiesManagement> list =
210         new ArrayList<SerializableConfigurationPropertiesManagement>();
211     for (final ConfigurationPropertiesManagement instance : configurationProperties)
212     {
213       final SerializableConfigurationPropertiesManagement serial =
214           instance.toSerializable();
215       list.add(serial);
216     }
217     return list;
218   }
219 
220   // --- get&set --------------------------------------------------------------
221 
222   @Override
223   public boolean isInAdminMode()
224   {
225     return representative().isInAdminMode();
226   }
227 
228   @Override
229   public void setToAdminMode(final boolean newMode)
230   {
231     synchronized (configurationProperties)
232     {
233       for (final ConfigurationPropertiesManagement instance : configurationProperties)
234       {
235         instance.setToAdminMode(newMode);
236       }
237     }
238   }
239 
240   @Override
241   public PropertyStoreAccessor getPropertyStoreAccessor()
242   {
243     return propertyStoreAccessor;
244   }
245 
246   // --- business -------------------------------------------------------------
247 
248   @Override
249   public DescribedProperty getProperty(final PropertyDescriptor descriptor,
250       final Object defaultValue) throws IllegalArgumentException,
251     UnknownPropertyException
252   {
253     DescribedProperty property = null;
254     for (final ConfigurationPropertiesManagement instance : configurationProperties)
255     {
256       try
257       {
258         property = instance.getProperty(descriptor, null);
259         if (property != null && property.getValue() != null)
260         {
261           break;
262         }
263       }
264       catch (final PropertyRootException e)
265       {
266         // continue to see if the next has a proper value.
267       }
268     }
269 
270     if (property == null || property.getValue() == null)
271     {
272       property = representative().getProperty(descriptor, defaultValue);
273     }
274 
275     return property;
276   }
277 
278   @Override
279   public DescribedProperty getProperty(final String key,
280       final Object defaultValue) throws IllegalArgumentException,
281     UnknownPropertyException
282   {
283     DescribedProperty property = null;
284     for (final ConfigurationPropertiesManagement instance : configurationProperties)
285     {
286       try
287       {
288         property = instance.getProperty(key, null);
289         if (property != null)
290         {
291           break;
292         }
293       }
294       catch (final PropertyRootException e)
295       {
296         // continue to see if the next has a proper value.
297       }
298     }
299 
300     if (property == null)
301     {
302       property = representative().getProperty(key, defaultValue);
303     }
304 
305     return property;
306   }
307 
308   @Override
309   public Object getPropertyAsType(final PropertyDescriptor descriptor)
310     throws IllegalArgumentException, UnknownPropertyException,
311     PropertyValueConversionException, SecurityException, PropertyRootException
312   {
313     final String plainValue = getPropertyValueAsString(descriptor);
314     final Object resolvedValue =
315         resolveAndConvert(descriptor, plainValue, null);
316     return resolvedValue;
317   }
318 
319   // Duplicated from AbstractConfigurationPropertiesManagement
320   @Override
321   public ValidatedProperty getValidatedProperty(
322       final PropertyDescriptor descriptor, final Object defaultValue)
323     throws IllegalArgumentException, UnknownPropertyException,
324     PropertyValidationException, SecurityException
325   {
326     final DescribedProperty property = getProperty(descriptor, defaultValue);
327 
328     final Object plainValue = property.getValue();
329     if (plainValue == null && defaultValue == null && descriptor.isMandatory()
330         && !isInAdminMode())
331     {
332       // We do not required that properties with a value of 'null' are specified
333       // explicitly. Therefore we must signal an unknown property only if it is
334       // mandatory.
335 
336       final PropertyValidationMessageBean message =
337           new PropertyValidationMessageBean(descriptor,
338               descriptor.getConstraints(), plainValue);
339       throw new PropertyValidationException(message);
340     }
341 
342     try
343     {
344       final Object resolvedValue =
345           resolveAndConvertAndValidate(descriptor, defaultValue, plainValue);
346 
347       return new ValidatedProperty(property, getExpression(descriptor),
348           resolvedValue);
349     }
350     catch (final PropertyValueResolveException e)
351     {
352       throw wrapWithPropertyValueSourceInformation(e, descriptor, property);
353     }
354     catch (final PropertyValueConversionException e)
355     {
356       throw wrapWithPropertyValueSourceInformation(e, property);
357     }
358     catch (final PropertyValidationException e)
359     {
360       throw wrapWithPropertyValueSourceInformation(e, property);
361     }
362   }
363 
364   private static String getExpression(final PropertyDescriptor descriptor)
365   {
366     final PropertyExpression expression = descriptor.getDefaultExpression();
367     return expression != null ? expression.getExpression() : null;
368   }
369 
370   private static PropertyValueResolveException wrapWithPropertyValueSourceInformation(
371       final PropertyValueResolveException e,
372       final PropertyDescriptor descriptor, final Property property)
373   {
374     final PropertyValueResolveException cause =
375         isResolvementFailureOfPlaceholder(e, property)
376             ? new PropertyValueResolveException(
377                 new PropertyExpressionMessageBean(e, descriptor,
378                     property.getValue())) : e;
379 
380     if (!(cause instanceof PropertyValueResolveWithSourceException))
381     {
382       final PropertyLocation source = property.getSource();
383       return new PropertyValueResolveWithSourceException(
384           new PropertyExpressionWithSourceMessageBean(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().toString());
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(
401         new PropertyValueWithSourceMessageBean(e, source));
402   }
403 
404   private static PropertyValidationException wrapWithPropertyValueSourceInformation(
405       final PropertyValidationException e, final Property property)
406   {
407     final PropertyLocation source = property.getSource();
408     return new PropertyValidationWithSourceException(
409         new PropertyValidationWithSourceMessageBean(e, source));
410   }
411 
412   @Override
413   public void validate(final boolean lenient, final Class<?>... groups)
414     throws ConfigurationValidationException
415   {
416     synchronized (configurationProperties)
417     {
418       final ConfigurationValidator validator =
419           new ConfigurationValidator(this, lenient);
420       final PropertyCollection propertyCollection =
421           getPropertyStoreAccessor().getPropertyCollectionFromStore();
422       validator.validate(propertyCollection, groups);
423     }
424   }
425 
426   @Override
427   public void validate(final PropertyDescriptor descriptor,
428       final Class<?>... groups) throws ConfigurationValidationException
429   {
430     final Object value = getPropertyValue(descriptor);
431     validate(descriptor, value, groups);
432   }
433 
434   @Override
435   public void validate(final PropertyDescriptor descriptor, final String value,
436       final Class<?>... groups) throws ConfigurationValidationException
437   {
438     final ConfigurationValidator validator =
439         new ConfigurationValidator(this, false);
440     final Object resolvedValue = resolveAndConvert(descriptor, value, null);
441     validator.validate(descriptor, resolvedValue, groups);
442   }
443 
444   @Override
445   public void flush()
446   {
447     for (final ConfigurationPropertiesManagement instance : configurationProperties)
448     {
449       instance.flush();
450     }
451   }
452 
453   @Override
454   public ConfigurationPropertiesManagement toRepresentative()
455   {
456     return representative();
457   }
458 
459   @Override
460   public SerializableConfigurationPropertiesManagement toSerializable()
461   {
462     return this;
463   }
464 
465   // ... delegate to representative ...........................................
466 
467   private ConfigurationPropertiesManagement representative()
468   {
469     return configurationProperties.get(0);
470   }
471 
472   @Override
473   public ConfigurationPropertiesManagement addDefinitions(
474       final PropertyProvider properties) throws NullPointerException
475   {
476     return representative().addDefinitions(properties);
477   }
478 
479   /**
480    * {@inheritDoc}
481    * <p>
482    * This method sets the property in the most defining representative. If the
483    * client wants to change a value in a specific defaulting representation by
484    * its configuration key, the client has to access this particular instance
485    * and set it manually.
486    * </p>
487    */
488   @Override
489   public Property setProperty(final PropertyKey key, final String value)
490     throws NullPointerException, PropertyValidationException,
491     ReadOnlyPropertyException
492   {
493     return representative().setProperty(key, value);
494   }
495 
496   /**
497    * {@inheritDoc}
498    * <p>
499    * This method unsets the property in the most defining representative. If the
500    * client wants to change a value in a specific defaulting representation by
501    * its configuration key, the client has to access this particular instance
502    * and unset it manually.
503    * </p>
504    */
505   @Override
506   public Property unsetProperty(final PropertyKey key)
507     throws NullPointerException, ReadOnlyPropertyException
508   {
509     return representative().unsetProperty(key);
510   }
511 
512   @Override
513   public void addDescriptors(final Class<?> declaringType)
514     throws DuplicatePropertyDeclarationsException
515   {
516     representative().addDescriptors(declaringType);
517   }
518 
519   @Override
520   public PropertyDescriptor getDescriptor(final String key)
521     throws UnknownPropertyException
522   {
523     return representative().getDescriptor(key);
524   }
525 
526   @Override
527   public PropertyDescriptor getDescriptor(final PropertyKey key)
528     throws UnknownPropertyException
529   {
530     return representative().getDescriptor(key);
531   }
532 
533   @Override
534   public List<PropertyDescriptor> getMandatoryPropertyDescriptors()
535   {
536     return representative().getMandatoryPropertyDescriptors();
537   }
538 
539   // --- object basics --------------------------------------------------------
540 
541   /**
542    * Returns the string representation of the object.
543    *
544    * @return the string representation of the object.
545    */
546   @Override
547   public String toString()
548   {
549     final StringBuilder buffer = new StringBuilder();
550 
551     for (final SerializableConfigurationPropertiesManagement instance : configurationProperties)
552     {
553       buffer.append(instance).append("\n======================\n");
554     }
555 
556     return buffer.toString();
557   }
558 }