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.beans.PropertyChangeListener;
19  import java.beans.PropertyChangeSupport;
20  import java.io.IOException;
21  import java.io.ObjectInputStream;
22  
23  import javax.annotation.concurrent.ThreadSafe;
24  
25  import org.apache.commons.lang.ObjectUtils;
26  
27  import de.smartics.properties.api.config.domain.ConfigurationProperties;
28  import de.smartics.properties.api.config.domain.ConfigurationValidationException;
29  import de.smartics.properties.api.config.domain.DescribedProperty;
30  import de.smartics.properties.api.config.domain.SerializableConfigurationProperties;
31  import de.smartics.properties.api.config.domain.UnknownPropertyException;
32  import de.smartics.properties.api.config.domain.ValidatedProperty;
33  import de.smartics.properties.api.config.domain.key.ConfigurationKey;
34  import de.smartics.properties.api.core.domain.PropertiesContext;
35  import de.smartics.properties.api.core.domain.PropertyContext;
36  import de.smartics.properties.api.core.domain.PropertyDescriptor;
37  import de.smartics.properties.api.core.domain.PropertyDescriptorRegistry;
38  import de.smartics.properties.api.core.domain.PropertyExpression;
39  import de.smartics.properties.api.core.domain.PropertyExpressionMessageBean;
40  import de.smartics.properties.api.core.domain.PropertyKey;
41  import de.smartics.properties.api.core.domain.PropertyValidationException;
42  import de.smartics.properties.api.core.domain.PropertyValueConversionException;
43  import de.smartics.properties.api.core.domain.PropertyValueResolveException;
44  import de.smartics.properties.api.core.security.PropertyValueSecurity;
45  import de.smartics.properties.api.core.security.SecurityException;
46  import de.smartics.properties.spi.config.proxy.PropertyConfigurationObjectBuilder;
47  import de.smartics.properties.spi.config.resolve.PropertyValueResolver;
48  import de.smartics.properties.spi.config.resolve.ResolveConfigurationException;
49  import de.smartics.properties.spi.config.resolve.SimplePropertyValueResolver;
50  import de.smartics.properties.spi.core.context.MandatoryPropertyContext;
51  import de.smartics.properties.spi.core.convert.BeanUtilsPropertyValueConverter;
52  import de.smartics.properties.spi.core.convert.PropertyValueConverter;
53  import de.smartics.properties.spi.core.validate.PropertyValidator;
54  import de.smartics.util.lang.Arg;
55  import de.smartics.util.lang.BlankArgumentException;
56  import de.smartics.util.lang.NullArgumentException;
57  
58  /**
59   * Abstract implementation of the {@link ConfigurationProperties} interface.
60   */
61  @ThreadSafe
62  public abstract class AbstractConfigurationProperties implements
63      ConfigurationProperties, ConfigurationPropertiesSpi
64  { // NOPMD
65    // ********************************* Fields *********************************
66  
67    // --- constants ------------------------------------------------------------
68  
69    // --- members --------------------------------------------------------------
70  
71    /**
72     * The key that identifies the configuration.
73     *
74     * @serial
75     */
76    private final ConfigurationKey<?> key;
77  
78    /**
79     * The registry to resolve property descriptors.
80     *
81     * @serial
82     */
83    private final PropertyDescriptorRegistry registry;
84  
85    /**
86     * The helper to decrypt secured property values.
87     *
88     * @serial
89     */
90    private final PropertyValueSecurity decrypter;
91  
92    /**
93     * The property value converter to and from Strings.
94     *
95     * @serial
96     */
97    private final PropertyValueConverter converter =
98        new BeanUtilsPropertyValueConverter();
99  
100   /**
101    * The resolver to resolve property value expressions.
102    */
103   private transient PropertyValueResolver resolver;
104 
105   /**
106    * Helper to handle property change listeners.
107    *
108    * @serial
109    */
110   private final PropertyChangeSupport support = new PropertyChangeSupport(this);
111 
112   // ****************************** Initializer *******************************
113 
114   // ****************************** Constructor ******************************
115 
116   /**
117    * Constructor for serializable subclasses.
118    */
119   protected AbstractConfigurationProperties()
120   {
121     this.key = null;
122     this.registry = null;
123     this.resolver = null;
124     this.decrypter = null;
125   }
126 
127   /**
128    * Default constructor.
129    *
130    * @param key the key that identifies the configuration.
131    * @param registry the registry to resolve property descriptors.
132    * @param decrypter the helper to decrypt secured property values.
133    * @throws NullArgumentException if {@code key}, {@code registry} or
134    *           {@code decrypter} is <code>null</code>.
135    */
136   protected AbstractConfigurationProperties(final ConfigurationKey<?> key,
137       final PropertyDescriptorRegistry registry,
138       final PropertyValueSecurity decrypter) throws NullArgumentException
139   {
140     this.key = Arg.checkNotNull("key", key);
141     this.registry = Arg.checkNotNull("registry", registry);
142     this.decrypter = Arg.checkNotNull("decrypter", decrypter);
143     this.resolver = createResolver();
144   }
145 
146   // ****************************** Inner Classes *****************************
147 
148   // ********************************* Methods ********************************
149 
150   // --- init -----------------------------------------------------------------
151 
152   private SimplePropertyValueResolver createResolver()
153   {
154     return new SimplePropertyValueResolver(registry, this);
155   }
156 
157   // --- get&set --------------------------------------------------------------
158 
159   @Override
160   public final ConfigurationKey<?> getKey()
161   {
162     return key;
163   }
164 
165   @Override
166   public final PropertyContext getContext(final PropertyDescriptor descriptor)
167     throws NullPointerException
168   {
169     Arg.checkNotNull("descriptor", descriptor);
170     return new MandatoryPropertyContext(registry.getContext(descriptor),
171         descriptor);
172   }
173 
174   @Override
175   public final PropertiesContext getContext(final Class<?> declaringType)
176     throws NullPointerException
177   {
178     Arg.checkNotNull("declaringType", declaringType);
179     return registry.getContext(declaringType);
180   }
181 
182   /**
183    * Returns the registry to resolve property descriptors.
184    *
185    * @return the registry to resolve property descriptors.
186    */
187   public final PropertyDescriptorRegistry getRegistry()
188   {
189     return registry;
190   }
191 
192   /**
193    * Returns the property value security helper to en- and decrypt property
194    * values.
195    *
196    * @return the property value security helper to en- and decrypt property
197    *         values.
198    */
199   public final PropertyValueSecurity getPropertyValueSecurity()
200   {
201     return decrypter;
202   }
203 
204   // --- business -------------------------------------------------------------
205 
206   @Override
207   public final DescribedProperty getProperty(final String key)
208     throws IllegalArgumentException, UnknownPropertyException
209   {
210     final PropertyDescriptor descriptor = getPropertyDescriptor(key);
211     return getProperty(descriptor);
212   }
213 
214   @Override
215   public DescribedProperty getProperty(final String key,
216       final Object defaultValue) throws IllegalArgumentException,
217     UnknownPropertyException
218   {
219     final PropertyDescriptor descriptor = getPropertyDescriptor(key);
220     return getProperty(descriptor, defaultValue);
221   }
222 
223   @Override
224   public final DescribedProperty getProperty(final PropertyKey key)
225     throws IllegalArgumentException, UnknownPropertyException
226   {
227     final PropertyDescriptor descriptor = getPropertyDescriptor(key.toString());
228     return getProperty(descriptor);
229   }
230 
231   @Override
232   public final DescribedProperty getProperty(final PropertyDescriptor descriptor)
233     throws IllegalArgumentException, UnknownPropertyException
234   {
235     return getProperty(descriptor, null);
236   }
237 
238   @Override
239   public final <T> T getProperties(final Class<T> propertiesInterface)
240   {
241     return getProperties(propertiesInterface, toSerializable());
242   }
243 
244   @Override
245   public final <T> T getProperties(final Class<T> propertiesInterface,
246       final SerializableConfigurationProperties configuration)
247   {
248     final PropertyConfigurationObjectBuilder builder =
249         new PropertyConfigurationObjectBuilder();
250     return builder.build(propertiesInterface, configuration);
251   }
252 
253   @Override
254   public final Object getPropertyValue(final String key)
255     throws IllegalArgumentException, UnknownPropertyException,
256     PropertyValidationException
257   {
258     final PropertyDescriptor descriptor = getPropertyDescriptor(key);
259     return getPropertyValue(descriptor, null);
260   }
261 
262   @Override
263   public final Object getPropertyValue(final PropertyKey key)
264     throws IllegalArgumentException, UnknownPropertyException,
265     PropertyValidationException
266   {
267     final PropertyDescriptor descriptor = getPropertyDescriptor(key.toString());
268     return getPropertyValue(descriptor, null);
269   }
270 
271   @Override
272   public final Object getPropertyValue(final String key,
273       final Object defaultValue) throws NullArgumentException,
274     PropertyValidationException
275   {
276     final PropertyDescriptor descriptor = getPropertyDescriptor(key);
277     return getPropertyValue(descriptor, defaultValue);
278   }
279 
280   @Override
281   public final String getPropertyValueAsString(final String key)
282     throws NullArgumentException, PropertyValidationException
283   {
284     return getPropertyValue(key).toString();
285   }
286 
287   @Override
288   public final Object getPropertyValue(final PropertyDescriptor descriptor,
289       final Object defaultValue) throws NullPointerException,
290     PropertyValueConversionException, PropertyValidationException,
291     UnknownPropertyException
292   {
293     return getValidatedProperty(descriptor, defaultValue).getValidatedValue();
294   }
295 
296   @Override
297   public final String getPropertyValueAsString(final String key,
298       final Object defaultValue) throws NullArgumentException,
299     PropertyValidationException
300   {
301     return getPropertyValue(key, defaultValue).toString();
302   }
303 
304   @Override
305   public final Object getPropertyValue(final PropertyDescriptor descriptor)
306     throws NullArgumentException, PropertyValidationException
307   {
308     return getPropertyValue(descriptor, null);
309   }
310 
311   @Override
312   public final String getPropertyValueAsString(
313       final PropertyDescriptor descriptor) throws NullArgumentException,
314     PropertyValidationException
315   {
316     return ObjectUtils.toString(getPropertyValue(descriptor, null), null);
317   }
318 
319   @Override
320   public final String getPropertyValueAsString(
321       final PropertyDescriptor descriptor, final Object defaultValue)
322     throws NullArgumentException, PropertyValidationException
323   {
324     return ObjectUtils.toString(getPropertyValue(descriptor, defaultValue),
325         null);
326   }
327 
328   @Override
329   public final ValidatedProperty getValidatedProperty(final PropertyKey key,
330       final Object defaultValue) throws IllegalArgumentException,
331     UnknownPropertyException, PropertyValidationException, SecurityException
332   {
333     final PropertyDescriptor descriptor = getPropertyDescriptor(key.toString());
334     return getValidatedProperty(descriptor, defaultValue);
335   }
336 
337   @Override
338   public final ValidatedProperty getValidatedProperty(final String key,
339       final Object defaultValue) throws IllegalArgumentException,
340     UnknownPropertyException, PropertyValidationException, SecurityException
341   {
342     final PropertyDescriptor descriptor = getPropertyDescriptor(key);
343     return getValidatedProperty(descriptor, defaultValue);
344   }
345 
346   /**
347    * Returns the property descriptor for a given key.
348    *
349    * @param key the key for which the property descriptor is needed.
350    * @return the property descriptor for the given key.
351    * @throws BlankArgumentException when the key is blank.
352    * @throws PropertyValidationException when the configuration is invalid.
353    */
354   protected final PropertyDescriptor getPropertyDescriptor(final String key)
355     throws BlankArgumentException, PropertyValidationException
356   {
357     Arg.checkNotBlank("key", key);
358 
359     final PropertyDescriptor descriptor = registry.get(key);
360     if (descriptor == null)
361     {
362       throw new UnknownPropertyException(getKey(), key);
363     }
364     return descriptor;
365   }
366 
367   /**
368    * Resolves, converts and validates the given value.
369    *
370    * @param descriptor the property descriptor.
371    * @param defaultValue the default value to use if {@code value} is
372    *          <code>null</code> and there is no default expression.
373    * @param value the value to resolve, convert and validate.
374    * @return the resolved, converted and validated value.
375    * @throws PropertyValueConversionException if the value cannot be converted
376    *           from {@link String} to the target type. If the configuration is
377    *           set to admin mode, the unresolved {@code value} will be returned.
378    *           In this case the type is not of the target type as described by
379    *           the descriptor.
380    * @throws PropertyValidationException if at least on constraint is not met.
381    * @throws SecurityException on any problem decrypting an encrypted value.
382    */
383   protected final Object resolveAndConvertAndValidate(
384       final PropertyDescriptor descriptor, final Object defaultValue,
385       final Object value) throws PropertyValueConversionException,
386     PropertyValidationException, SecurityException
387   {
388     try
389     {
390       final Object convertedValue =
391           resolveAndConvert(descriptor, value, defaultValue);
392       validate(descriptor, convertedValue);
393 
394       return convertedValue;
395     }
396     catch (final PropertyValueConversionException e)
397     {
398       if (isInAdminMode())
399       {
400         return value;
401       }
402       throw e;
403     }
404     catch (final PropertyValueResolveException e)
405     {
406       if (isInAdminMode())
407       {
408         return value;
409       }
410       throw e;
411     }
412   }
413 
414   /**
415    * Validates the given value for the given property.
416    *
417    * @param descriptor the descriptor to the property to use for validation.
418    * @param value the property value to validate.
419    * @param ifInOneOfTheseGroups the validation groups to consider in the
420    *          validation process. The groups will be validated in the given
421    *          order. As soon as a validation group fails, the validation process
422    *          is aborted without checking the not yet processed groups.
423    */
424   public void validate(final PropertyDescriptor descriptor, final Object value,
425       final Class<?>... ifInOneOfTheseGroups)
426   {
427     final PropertyValidator validator = new PropertyValidator(isInAdminMode());
428     validator.validate(descriptor, value, ifInOneOfTheseGroups);
429   }
430 
431   /**
432    * Resolves and converts the given value.
433    *
434    * @param descriptor the property descriptor.
435    * @param value the value to resolve and convert.
436    * @param defaultValue the default value to use if {@code value} is
437    *          <code>null</code> and there is no default expression.
438    * @return the resolved and converted value.
439    * @throws PropertyValueResolveException if the value cannot be resolved.
440    * @throws PropertyValueConversionException if the value cannot be converted
441    *           from {@link String} to the target type.
442    * @throws SecurityException on any problem decrypting an encrypted value.
443    */
444   protected final Object resolveAndConvert(final PropertyDescriptor descriptor,
445       final Object value, final Object defaultValue)
446     throws PropertyValueResolveException, PropertyValueConversionException,
447     SecurityException
448   {
449     Object currentValue = value;
450     if (currentValue == null)
451     {
452       currentValue = descriptor.getDefaultExpression();
453       if (currentValue == null)
454       {
455         currentValue = defaultValue;
456       }
457     }
458 
459     if (currentValue instanceof PropertyExpression)
460     {
461       final PropertyExpression expression = (PropertyExpression) currentValue;
462       currentValue = expression.getExpression();
463     }
464 
465     if (currentValue instanceof String)
466     {
467       currentValue = resolveValue(descriptor, (String) currentValue);
468     }
469 
470     if (descriptor.isSecured() && currentValue instanceof String)
471     {
472       currentValue = decrypter.decrypt(descriptor, (String) currentValue);
473     }
474 
475     final Object convertedValue = convert(descriptor, currentValue);
476     return convertedValue;
477   }
478 
479   private String resolveValue(final PropertyDescriptor descriptor,
480       final String value)
481   {
482     try
483     {
484       final String resolvedValue = resolver.resolve((String) value);
485       return resolvedValue;
486     }
487     catch (final ResolveConfigurationException e)
488     {
489       throw new PropertyValueResolveException(
490           new PropertyExpressionMessageBean(e, descriptor, e.getExpression()));
491     }
492   }
493 
494   private Object convert(final PropertyDescriptor descriptor,
495       final Object currentValue)
496   {
497     if (currentValue == null)
498     {
499       return null;
500     }
501 
502     return converter.convert(descriptor, currentValue);
503   }
504 
505   @Override
506   public final void validate() throws ConfigurationValidationException
507   {
508     validate(false);
509   }
510 
511   @Override
512   public final void validate(final Class<?>... groups)
513     throws ConfigurationValidationException
514   {
515     validate(false, groups);
516   }
517 
518   // ... change listener support ..............................................
519 
520   @Override
521   public final void addPropertyChangeListener(final PropertyKey name,
522       final PropertyChangeListener listener) throws NullPointerException
523   {
524     support.addPropertyChangeListener(name.toString(), listener);
525   }
526 
527   @Override
528   public final void removePropertyChangeListener(final PropertyKey name,
529       final PropertyChangeListener listener) throws NullPointerException
530   {
531     support.removePropertyChangeListener(name.toString(), listener);
532   }
533 
534   @Override
535   public final void addPropertyChangeListener(
536       final PropertyChangeListener listener) throws NullPointerException
537   {
538     support.addPropertyChangeListener(listener);
539   }
540 
541   @Override
542   public final void removePropertyChangeListener(
543       final PropertyChangeListener listener) throws NullPointerException
544   {
545     support.removePropertyChangeListener(listener);
546   }
547 
548   /**
549    * Fires the property change event.
550    *
551    * @param name the name of the property.
552    * @param oldValue the old value of the property that has been changed.
553    * @param newValue the new and current value of the property.
554    */
555   protected final void firePropertyChange(final String name,
556       final String oldValue, final String newValue)
557   {
558     support.firePropertyChange(name, oldValue, newValue);
559   }
560 
561   // --- object basics --------------------------------------------------------
562 
563   @Override
564   public ConfigurationProperties toRepresentative()
565   {
566     return this;
567   }
568 
569   /**
570    * Reads the object from the given stream.
571    *
572    * @param in the stream to read from.
573    * @throws IOException on read problems.
574    * @throws ClassNotFoundException if a class cannot be found.
575    */
576   private void readObject(final ObjectInputStream in) throws IOException,
577     ClassNotFoundException
578   {
579     in.defaultReadObject();
580 
581     this.resolver = createResolver();
582   }
583 
584   /**
585    * Returns the string representation of the object.
586    *
587    * @return the string representation of the object.
588    */
589   @Override
590   public String toString()
591   {
592     final StringBuilder buffer = new StringBuilder();
593 
594     buffer.append(key).append(":\n").append("Registry:\n").append(registry);
595 
596     return buffer.toString();
597   }
598 }