View Javadoc

1   package net.sf.chainedoptions;
2   
3   import java.beans.PropertyEditor;
4   import java.beans.PropertyEditorManager;
5   import java.util.Iterator;
6   import java.util.List;
7   
8   import org.apache.commons.lang.StringUtils;
9   import org.springframework.beans.BeanWrapper;
10  import org.springframework.beans.BeanWrapperImpl;
11  import org.springframework.beans.PropertyValue;
12  import org.springframework.beans.factory.InitializingBean;
13  
14  /***
15   * Abstract class that implements the ChainedOption interface and provides an
16   * implementation of {@link #updateValue(Object, List, Object)}. This
17   * implementation loops through the list of options and checks if the
18   * <code>commandProperty</code> in the <code>command</code> object matches
19   * any item in the list. If it doesn't, a default value is set in the command
20   * object. The default value is provided by the <code>ChainedOptionStrategy</code>
21   * retrieved from the {@link #getStrategy(Object)}method.
22   * <p>
23   * Subclasses must implement the method {@link #retrieveOptions(Object, Object)}
24   * method. Below is an example of such an implementation:
25   * 
26   * <pre>
27   * package com.acme.valuehandler;
28   * 
29   * import java.util.List;
30   * import com.acme.valuehandler.config.RegionConfig;
31   * 
32   * public class CountryChainedOption extends AbstractChainedOption {
33   * 
34   *     private RegionConfig regionConfig;
35   * 
36   *     private String regionProperty;
37   * 
38   *     public List retrieveOptions(Object command) {
39   *         String region = getProperty(command, regionProperty);
40   *         List countryBeans = regionConfig.getCountries(region);
41   *         List countries = getConverter().convert(countryBeans);
42   *         return getStrategy().adjustAndSort(countries);
43   *     }
44   * }
45   * </pre>
46   * 
47   * @author Mattias Arthursson
48   * @author Ulrik Sandberg
49   */
50  public abstract class AbstractChainedOption implements ChainedOption,
51          InitializingBean {
52  
53      private String commandProperty;
54  
55      private String optionsKey;
56  
57      private BeanConverter converter;
58  
59      private ChainedOptionStrategy defaultStrategy;
60  
61      public abstract List retrieveOptions(Object command, Object context);
62  
63      /***
64       * Default implementation of <code>updateValue</code>, which loops
65       * through the list of available options and compares them to the current
66       * value of the managed attribute in the <code>command</code> object.
67       * <p />
68       * If the selected value is present in the list, no modifications are done
69       * in the <code>command</code>, otherwise a default value is retrieved by
70       * calling {@link ChainedOptionStrategy#getDefaultValue(List, Object)}, and
71       * the <code>commandProperty</code> in the <code>command</code> is set
72       * to this value.
73       * 
74       * @param command
75       *            the object which will possibly be updated with a new object.
76       * @param options
77       *            the list of options with which the managed value in the
78       *            <code>command</code> will be compared.
79       * @param context
80       *            a context that will be supplied to the
81       *            <code>ChainedOptionStrategy</code> for selecting an
82       *            appropriate default value.
83       * @see ChainedOptionStrategy#getDefaultValue(List, Object)
84       */
85      public void updateValue(Object command, List options, Object context) {
86          Object selectedValue = getProperty(command, getCommandProperty());
87          if (selectedValue != null) {
88              PropertyEditor propertyEditor = PropertyEditorManager
89                      .findEditor(selectedValue.getClass());
90              String selectedValueAsString;
91              if (propertyEditor != null) {
92                  propertyEditor.setValue(selectedValue);
93                  selectedValueAsString = propertyEditor.getAsText();
94              } else {
95                  selectedValueAsString = (String) selectedValue;
96              }
97  
98              for (Iterator iter = options.iterator(); iter.hasNext();) {
99                  if (matches((LabelValueBean) iter.next(), selectedValueAsString)) {
100                     return;
101                 }
102             }
103         }
104 
105         setProperty(command, getCommandProperty(), getStrategy(command)
106                 .getDefaultValue(options, context));
107     }
108 
109     /***
110      * Utility method that matches the value of the given <code>bean</code>
111      * with the specified <code>value</code>.
112      * 
113      * @param bean
114      *            The {@link LabelValueBean}to match the value against.
115      * @param value
116      *            The value to match.
117      * @return <code>true</code> if the values match.
118      */
119     protected boolean matches(LabelValueBean bean, String value) {
120         return StringUtils.equals(((LabelValueBean) bean).getValue(), value);
121     }
122 
123     /***
124      * Checks if all necessary properties have been set. Subclasses should
125      * override {@link #initChainedOption()}if additional integrity checks are
126      * needed on initialization.
127      * 
128      * @throws IllegalArgumentException
129      *             if any mandatory property has not been set.
130      */
131     public final void afterPropertiesSet() throws Exception {
132         if (commandProperty == null) {
133             throw new IllegalArgumentException(
134                     "Property 'commandProperty' must be set");
135         }
136         if (optionsKey == null) {
137             throw new IllegalArgumentException(
138                     "Property 'optionsKey' must be set");
139         }
140         if (defaultStrategy == null) {
141             throw new IllegalArgumentException(
142                     "Property 'defaultStrategy' must be set");
143         }
144         initChainedOption();
145     }
146 
147     /***
148      * Template method that subclasses may implement to ensure proper
149      * initialization. This method is called after all properties has been set.
150      */
151     protected void initChainedOption() {
152     }
153 
154     /***
155      * Utility method that sets a named property on a given object.
156      * 
157      * @param bean
158      *            The object to set the property on.
159      * @param propertyName
160      *            The name of the property to set.
161      * @param value
162      *            The value that the property will be set to.
163      */
164     protected void setProperty(Object bean, String propertyName, Object value) {
165         BeanWrapper commandWrapper = new BeanWrapperImpl(bean);
166         commandWrapper.setPropertyValue(new PropertyValue(propertyName, value));
167     }
168 
169     /***
170      * Utility method that gets a named property from a given object.
171      * 
172      * @param bean
173      *            The object to get the property from.
174      * @param property
175      *            The name of the property to get.
176      * @return The property value.
177      */
178     protected Object getProperty(Object bean, String property) {
179         BeanWrapper commandWrapper = new BeanWrapperImpl(bean);
180         return commandWrapper.getPropertyValue(property);
181     }
182 
183     /***
184      * @return Returns the commandProperty.
185      */
186     public String getCommandProperty() {
187         return commandProperty;
188     }
189 
190     /***
191      * Set the property on the target <code>command</code> object managed by
192      * this instance.
193      * 
194      * @param commandProperty
195      *            The commandProperty to set.
196      */
197     public void setCommandProperty(String commandProperty) {
198         this.commandProperty = commandProperty;
199     }
200 
201     /***
202      * @return Returns the optionsKey.
203      */
204     public String getOptionsKey() {
205         return optionsKey;
206     }
207 
208     /***
209      * Set the key that should identify the option list managed by this
210      * instance.
211      * 
212      * @param optionsKey
213      *            The optionsKey to set.
214      */
215     public void setOptionsKey(String optionsKey) {
216         this.optionsKey = optionsKey;
217     }
218 
219     /***
220      * @return Returns the converter.
221      */
222     public BeanConverter getConverter() {
223         return converter;
224     }
225 
226     /***
227      * Set the <code>BeanConverter</code> that should be used for translating
228      * to LabelValueBeans.
229      * 
230      * @param converter
231      *            The converter to set.
232      */
233     public void setConverter(BeanConverter converter) {
234         this.converter = converter;
235     }
236 
237     /***
238      * Override this if the implementation has several strategies.
239      * 
240      * @return this implementation returns the <code>defaultStrategy</code>.
241      */
242     public ChainedOptionStrategy getStrategy(Object command) {
243         return defaultStrategy;
244     }
245 
246     /***
247      * Set the default strategy to use.
248      * 
249      * @param defaultStrategy
250      *            the default strategy to set.
251      */
252     public void setDefaultStrategy(ChainedOptionStrategy defaultStrategy) {
253         this.defaultStrategy = defaultStrategy;
254     }
255 }