001 /*
002 * Copyright 2002-2004 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005 * use this file except in compliance with the License. You may obtain a copy of
006 * the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013 * License for the specific language governing permissions and limitations under
014 * the License.
015 */
016 package org.springframework.richclient.form.binding.support;
017
018 import java.util.ArrayList;
019 import java.util.HashMap;
020 import java.util.Iterator;
021 import java.util.List;
022 import java.util.Map;
023 import java.util.Properties;
024
025 import org.springframework.beans.factory.InitializingBean;
026 import org.springframework.beans.propertyeditors.ClassEditor;
027 import org.springframework.binding.form.FormModel;
028 import org.springframework.context.ApplicationContext;
029 import org.springframework.context.ApplicationContextAware;
030 import org.springframework.core.enums.LabeledEnum;
031 import org.springframework.richclient.form.binding.Binder;
032 import org.springframework.richclient.form.binding.BinderSelectionStrategy;
033 import org.springframework.richclient.util.ClassUtils;
034 import org.springframework.util.Assert;
035
036 /**
037 * Default implementation of <code>BinderSelectionStrategy</code>. Provides for
038 * registering of binders by control type, property type and property name.
039 *
040 * @author Oliver Hutchison
041 * @author Jim Moore
042 */
043 public abstract class AbstractBinderSelectionStrategy implements BinderSelectionStrategy, ApplicationContextAware, InitializingBean{
044
045 private final Class defaultControlType;
046
047 private final ClassEditor classEditor = new ClassEditor();
048
049 private final Map controlTypeBinders = new HashMap();
050
051 private final Map propertyTypeBinders = new HashMap();
052
053 private final Map propertyNameBinders = new HashMap();
054
055 private List bindersForPropertyNames = new ArrayList();
056
057 private ApplicationContext applicationContext;
058
059 public AbstractBinderSelectionStrategy(Class defaultControlType) {
060 this.defaultControlType = defaultControlType;
061 registerDefaultBinders();
062 }
063
064 public Binder selectBinder(FormModel formModel, String propertyName) {
065 // first try and find a binder for the specific property name
066 Binder binder = findBinderByPropertyName(formModel.getFormObject().getClass(), propertyName);
067 if (binder == null) {
068 // next try and find a binder for the specific property type
069 binder = findBinderByPropertyType(getPropertyType(formModel, propertyName));
070 }
071 if (binder == null) {
072 // just find a binder for the default control type
073 binder = selectBinder(defaultControlType, formModel, propertyName);
074 }
075 if (binder != null) {
076 return binder;
077 }
078 throw new UnsupportedOperationException("Unable to select a binder for form model [" + formModel
079 + "] property [" + propertyName + "]");
080 }
081
082 public Binder selectBinder(Class controlType, FormModel formModel, String propertyName) {
083 Binder binder = findBinderByControlType(controlType);
084 if (binder == null) {
085 binder = selectBinder(formModel, propertyName);
086 }
087 if (binder != null) {
088 return binder;
089 }
090 throw new UnsupportedOperationException("Unable to select a binder for form model [" + formModel
091 + "] property [" + propertyName + "]");
092 }
093
094 /**
095 * Register the default set of binders. This method is called on construction.
096 *
097 * @see #registerBinderForPropertyName(Class, String, Binder)
098 * @see #registerBinderForPropertyType(Class, Binder)
099 * @see #registerBinderForControlType(Class, Binder)
100 */
101 protected abstract void registerDefaultBinders();
102
103 /**
104 * Try to find a binder for the provided parentObjectType and propertyName. If no
105 * direct match found try to find binder for any superclass of the provided
106 * objectType which also has the same propertyName.
107 */
108 protected Binder findBinderByPropertyName(Class parentObjectType, String propertyName) {
109 PropertyNameKey key = new PropertyNameKey(parentObjectType, propertyName);
110 Binder binder = (Binder)propertyNameBinders.get(key);
111 if (binder == null) {
112 // if no direct match was found try to find a match in any super classes
113 final Map potentialMatchingBinders = new HashMap();
114 for (Iterator i = propertyNameBinders.entrySet().iterator(); i.hasNext();) {
115 Map.Entry entry = (Map.Entry)i.next();
116 if (((PropertyNameKey)entry.getKey()).getPropertyName().equals(propertyName)) {
117 potentialMatchingBinders.put(((PropertyNameKey)entry.getKey()).getParentObjectType(),
118 entry.getValue());
119 }
120 }
121 binder = (Binder)ClassUtils.getValueFromMapForClass(parentObjectType, potentialMatchingBinders);
122 if (binder != null) {
123 // remember the lookup so it doesn't have to be discovered again
124 registerBinderForPropertyName(parentObjectType, propertyName, binder);
125 }
126 }
127 return binder;
128 }
129
130 /**
131 * Try to find a binder for the provided propertyType. If no direct match found,
132 * try to find binder for closest superclass of the given control type.
133 */
134 protected Binder findBinderByPropertyType(Class propertyType) {
135 return (Binder)ClassUtils.getValueFromMapForClass(propertyType, propertyTypeBinders);
136 }
137
138 /**
139 * Try to find a binder for the provided controlType. If no direct match found,
140 * try to find binder for closest superclass of the given control type.
141 */
142 protected Binder findBinderByControlType(Class controlType) {
143 return (Binder)ClassUtils.getValueFromMapForClass(controlType, controlTypeBinders);
144 }
145
146 protected void registerBinderForPropertyName(Class parentObjectType, String propertyName, Binder binder) {
147 propertyNameBinders.put(new PropertyNameKey(parentObjectType, propertyName), binder);
148 }
149
150 /**
151 * Add a list of binders that are bound to propertyNames. Each element in the list should
152 * be a Properties element describing the binder and propertyName. For more information
153 * about the structure of the properties see {@link #setBinderForPropertyName(Properties)}.
154
155 * <br><br>
156 * <list><br>
157 * <props><br>
158 * <prop key="...">...</prop><br>
159 * <!-- More info in docs of setBinderForPropertyName(Properties)--><br>
160 * </props><br>
161 * </list><br>
162 *
163 * @param binders List of <code>Properties</code> elements
164 * @see #setBinderForPropertyName(Properties)
165 */
166 public void setBindersForPropertyNames(List binders)
167 {
168 bindersForPropertyNames = binders;
169 }
170
171 /**
172 * Create/link a <code>Binder</code> to a propertyName from the given <code>Properties</code>.
173 * <p>
174 * The used keys are:
175 * <ul>
176 * <li><b>objectClass</b>: The bean which has the property.
177 * <li><b>propertyName</b>: The property that will need the binder.
178 * <li><b>binder</b>: The Fully Qualified ClassName that will be used to instantiate the <code>Binder</code>.
179 * <li><b>binderRef</b>: The beanId that identifies the <code>Binder</code> which is defined elsewhere.
180 * </ul>
181 * <p>
182 * The first two keys are mandatory in combination with one of the two latter (binder or binderRef)
183 * The following two cases can be used to define a binder/propertyName combination:
184 * <br><br>
185 * <props><br>
186 * <prop key="objectClass">mypackage.MyBean</prop><br>
187 * <prop key="propertyName">myProperty</prop><br>
188 * <prop key="binder">mypackage.MyBinder</prop><br>
189 * </props><br>
190 * <br>
191 * <props><br>
192 * <prop key="objectClass">mypackage.MyBean</prop><br>
193 * <prop key="propertyName">myProperty</prop><br>
194 * <prop key="binderRef">myBinderBeanId</prop><br>
195 * <!-- myBinderBeanId identifies a bean defined elsewhere--><br>
196 * </props><br>
197 *
198 * @param binder The <code>Properties</code> object containing the correct keys.
199 */
200 public void setBinderForPropertyName(Properties binder)
201 {
202 String objectClassName = (String) binder.get("objectClass");
203 if (objectClassName == null)
204 throw new IllegalArgumentException("objectClass is required");
205
206 classEditor.setAsText(objectClassName);
207 Class objectClass = (Class) classEditor.getValue();
208
209 String propertyName = (String) binder.get("propertyName");
210 if (propertyName == null)
211 throw new IllegalArgumentException("propertyName is required");
212
213 if (binder.containsKey("binder"))
214 {
215 Object binderParameter = binder.get("binder");
216 classEditor.setAsText((String) binderParameter);
217 Class binderClass = (Class) classEditor.getValue();
218 try
219 {
220 registerBinderForPropertyName(objectClass, propertyName, (Binder) binderClass.newInstance());
221 }
222 catch (Exception e)
223 {
224 throw new IllegalArgumentException(
225 "Could not instantiate new binder with default constructor: " + binderParameter);
226 }
227 }
228 else if (binder.containsKey("binderRef"))
229 {
230 String binderID = (String) binder.get("binderRef");
231 Binder binderBean = (Binder) getApplicationContext().getBean(binderID);
232 registerBinderForPropertyName(objectClass, propertyName, binderBean);
233 }
234 else
235 throw new IllegalArgumentException("binder or binderRef is required");
236 }
237
238 protected void registerBinderForPropertyType(Class propertyType, Binder binder) {
239 propertyTypeBinders.put(propertyType, binder);
240 }
241
242 /**
243 * Registers property type binders by extracting the key and value from each entry
244 * in the provided map using the key to specify the property type and the value
245 * to specify the binder.
246 *
247 * <p>Binders specified in the provided map will override any binders previously
248 * registered for the same property type.
249 * @param binders the map containing the entries to register; keys must be of type
250 * <code>Class</code> and values of type <code>Binder</code>.
251 */
252 public void setBindersForPropertyTypes(Map binders) {
253 for (Iterator i = binders.entrySet().iterator(); i.hasNext();) {
254 Map.Entry entry = (Map.Entry)i.next();
255 registerBinderForPropertyType((Class)entry.getKey(), (Binder)entry.getValue());
256 }
257 }
258
259 protected void registerBinderForControlType(Class controlType, Binder binder) {
260 controlTypeBinders.put(controlType, binder);
261 }
262
263 /**
264 * Registers control type binders by extracting the key and value from each entry
265 * in the provided map using the key to specify the property type and the value
266 * to specify the binder.
267 *
268 * <p>Binders specified in the provided map will override any binders previously
269 * registered for the same control type.
270 * @param binders the map containing the entries to register; keys must be of type
271 * <code>Class</code> and values of type <code>Binder</code>.
272 */
273 public void setBindersForControlTypes(Map binders) {
274 for (Iterator i = binders.entrySet().iterator(); i.hasNext();) {
275 Map.Entry entry = (Map.Entry)i.next();
276 registerBinderForControlType((Class)entry.getKey(), (Binder)entry.getValue());
277 }
278 }
279
280 protected Class getPropertyType(FormModel formModel, String formPropertyPath) {
281 return formModel.getFieldMetadata(formPropertyPath).getPropertyType();
282 }
283
284 protected boolean isEnumeration(FormModel formModel, String formPropertyPath) {
285 return LabeledEnum.class.isAssignableFrom(getPropertyType(formModel, formPropertyPath));
286 }
287
288 private static class PropertyNameKey {
289 private final Class parentObjectType;
290
291 private final String propertyName;
292
293 public PropertyNameKey(Class parentObjectType, String propertyName) {
294 Assert.notNull(parentObjectType, "parentObjectType must not be null.");
295 Assert.notNull(propertyName, "propertyName must not be null.");
296 this.parentObjectType = parentObjectType;
297 this.propertyName = propertyName;
298 }
299
300 public String getPropertyName() {
301 return propertyName;
302 }
303
304 public Class getParentObjectType() {
305 return parentObjectType;
306 }
307
308 public boolean equals(Object o) {
309 if (this == o) {
310 return true;
311 }
312 if (!(o instanceof PropertyNameKey)) {
313 return false;
314 }
315 final PropertyNameKey propertyNameKey = (PropertyNameKey)o;
316 return propertyName.equals(propertyNameKey.propertyName)
317 && parentObjectType.equals(propertyNameKey.parentObjectType);
318 }
319
320 public int hashCode() {
321 return (propertyName.hashCode() * 29) + parentObjectType.hashCode();
322 }
323 }
324
325 public void setApplicationContext(ApplicationContext applicationContext)
326 {
327 this.applicationContext = applicationContext;
328 }
329
330 protected ApplicationContext getApplicationContext()
331 {
332 return applicationContext;
333 }
334
335 public void afterPropertiesSet() throws Exception
336 {
337 for (Iterator i = bindersForPropertyNames.iterator(); i.hasNext();)
338 {
339 setBinderForPropertyName((Properties) i.next());
340 }
341 }
342 }