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 }