View Javadoc

1   /**
2    * Copyright 2005-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
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 org.kuali.rice.krad.web.bind;
17  
18  import org.kuali.rice.krad.uif.view.ViewIndex;
19  import org.kuali.rice.krad.uif.view.ViewModel;
20  import org.springframework.beans.BeanWrapperImpl;
21  import org.springframework.beans.BeansException;
22  import org.springframework.beans.InvalidPropertyException;
23  import org.springframework.beans.PropertyAccessorUtils;
24  import org.springframework.beans.PropertyValue;
25  import org.springframework.util.StringUtils;
26  
27  import java.beans.PropertyDescriptor;
28  import java.beans.PropertyEditor;
29  import java.util.ArrayList;
30  import java.util.HashSet;
31  import java.util.List;
32  import java.util.Set;
33  
34  /**
35   * Class is a top level BeanWrapper for a UIF View Model
36   *
37   * <p>
38   * Registers custom property editors configured on the field associated with the property name for which
39   * we are getting or setting a value. In addition determines if the field requires encryption and if so applies
40   * the {@link UifEncryptionPropertyEditorWrapper}
41   * </p>
42   *
43   * @author Kuali Rice Team (rice.collab@kuali.org)
44   */
45  public class UifViewBeanWrapper extends BeanWrapperImpl {
46      private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(UifViewBeanWrapper.class);
47  
48      // this is a handle to the target object so we don't have to cast so often
49      private ViewModel model;
50  
51      // this stores all properties this wrapper has already checked
52      // with the view so the service isn't called again
53      private Set<String> processedProperties;
54  
55      public UifViewBeanWrapper(ViewModel model) {
56          super(model);
57  
58          this.model = model;
59          this.processedProperties = new HashSet<String>();
60      }
61  
62      /**
63       * Attempts to find a corresponding data field for the given property name in the current view or previous view,
64       * then if the field has a property editor configured it is registered with the property editor registry to use
65       * for this property
66       *
67       * @param propertyName - name of the property to find field and editor for
68       */
69      protected void registerEditorFromView(String propertyName) {
70          if (LOG.isDebugEnabled()) {
71              LOG.debug("Attempting to find property editor for property '" + propertyName + "'");
72          }
73  
74          // check if we already processed this property for this BeanWrapper instance
75          if (processedProperties.contains(propertyName)) {
76              return;
77          }
78  
79          // when rendering the page, we will use the view that was just built, for post
80          // we need to use the posted view (not the newly initialized view)
81          ViewIndex viewIndex = null;
82          if (model.getView() != null) {
83              viewIndex = model.getView().getViewIndex();
84          } else if (model.getPostedView() != null) {
85              viewIndex = model.getPostedView().getViewIndex();
86          }
87  
88          // if view index instance not established we cannot determine property editors
89          if (viewIndex == null) {
90              return;
91          }
92  
93          PropertyEditor propertyEditor = null;
94          boolean requiresEncryption = false;
95  
96          if (viewIndex.getFieldPropertyEditors().containsKey(propertyName)) {
97              propertyEditor = viewIndex.getFieldPropertyEditors().get(propertyName);
98          } else if (viewIndex.getSecureFieldPropertyEditors().containsKey(propertyName)) {
99              propertyEditor = viewIndex.getSecureFieldPropertyEditors().get(propertyName);
100             requiresEncryption = true;
101         }
102 
103         if (propertyEditor != null) {
104             if (LOG.isDebugEnabled()) {
105                 LOG.debug("Registering custom editor for property path '" + propertyName
106                         + "' and property editor class '" + propertyEditor.getClass().getName() + "'");
107             }
108 
109             if (requiresEncryption) {
110                 if (LOG.isDebugEnabled()) {
111                     LOG.debug("Enabling encryption for custom editor '" + propertyName +
112                             "' and property editor class '" + propertyEditor.getClass().getName() + "'");
113                 }
114                 this.registerCustomEditor(null, propertyName, new UifEncryptionPropertyEditorWrapper(propertyEditor));
115             } else {
116                 this.registerCustomEditor(null, propertyName, propertyEditor);
117             }
118         } else if (requiresEncryption) {
119             if (LOG.isDebugEnabled()) {
120                 LOG.debug("No custom formatter for property path '" + propertyName
121                         + "' but property does require encryption");
122             }
123 
124             this.registerCustomEditor(null, propertyName, new UifEncryptionPropertyEditorWrapper(
125                     findEditorForPropertyName(propertyName)));
126         }
127 
128         processedProperties.add(propertyName);
129     }
130 
131     protected PropertyEditor findEditorForPropertyName(String propertyName) {
132         Class<?> clazz = getPropertyType(propertyName);
133         if (LOG.isDebugEnabled()) {
134             LOG.debug("Attempting retrieval of property editor using class '"
135                     + clazz
136                     + "' and property path '"
137                     + propertyName
138                     + "'");
139         }
140 
141         PropertyEditor editor = findCustomEditor(clazz, propertyName);
142         if (editor == null) {
143             if (LOG.isDebugEnabled()) {
144                 LOG.debug("No custom property editor found using class '"
145                         + clazz
146                         + "' and property path '"
147                         + propertyName
148                         + "'. Attempting to find default property editor class.");
149             }
150             editor = getDefaultEditor(clazz);
151         }
152 
153         return editor;
154     }
155 
156     @Override
157     public Class<?> getPropertyType(String propertyName) throws BeansException {
158         try {
159             PropertyDescriptor pd = getPropertyDescriptorInternal(propertyName);
160             if (pd != null) {
161                 return pd.getPropertyType();
162             }
163 
164             // Maybe an indexed/mapped property...
165             Object value = super.getPropertyValue(propertyName);
166             if (value != null) {
167                 return value.getClass();
168             }
169 
170             // Check to see if there is a custom editor,
171             // which might give an indication on the desired target type.
172             Class<?> editorType = guessPropertyTypeFromEditors(propertyName);
173             if (editorType != null) {
174                 return editorType;
175             }
176         } catch (InvalidPropertyException ex) {
177             // Consider as not determinable.
178         }
179 
180         return null;
181     }
182 
183     @Override
184     protected BeanWrapperImpl getBeanWrapperForPropertyPath(String propertyPath) {
185         BeanWrapperImpl beanWrapper = super.getBeanWrapperForPropertyPath(propertyPath);
186 
187         PropertyTokenHolder tokens = getPropertyNameTokens(propertyPath);
188         String canonicalName = tokens.canonicalName;
189 
190         int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(canonicalName);
191         if (pos != -1) {
192             canonicalName = canonicalName.substring(0, pos);
193         }
194 
195         copyCustomEditorsTo(beanWrapper, canonicalName);
196 
197         return beanWrapper;
198     }
199 
200     @Override
201     public Object getPropertyValue(String propertyName) throws BeansException {
202         registerEditorFromView(propertyName);
203         return super.getPropertyValue(propertyName);
204     }
205 
206     @Override
207     public void setPropertyValue(PropertyValue pv) throws BeansException {
208         registerEditorFromView(pv.getName());
209         super.setPropertyValue(pv);
210     }
211 
212     @Override
213     public void setPropertyValue(String propertyName, Object value) throws BeansException {
214         registerEditorFromView(propertyName);
215         super.setPropertyValue(propertyName, value);
216     }
217 
218     @Override
219     public void setWrappedInstance(Object object, String nestedPath, Object rootObject) {
220         //TODO clear cache?
221         model = (ViewModel) object;
222         super.setWrappedInstance(object, nestedPath, rootObject);
223     }
224 
225     @Override
226     public void setWrappedInstance(Object object) {
227         //TODO clear cache?
228         model = (ViewModel) object;
229         super.setWrappedInstance(object);
230     }
231 
232     /**
233      * Parse the given property name into the corresponding property name tokens.
234      *
235      * @param propertyName the property name to parse
236      * @return representation of the parsed property tokens
237      */
238     private PropertyTokenHolder getPropertyNameTokens(String propertyName) {
239         PropertyTokenHolder tokens = new PropertyTokenHolder();
240         String actualName = null;
241         List<String> keys = new ArrayList<String>(2);
242         int searchIndex = 0;
243         while (searchIndex != -1) {
244             int keyStart = propertyName.indexOf(PROPERTY_KEY_PREFIX, searchIndex);
245             searchIndex = -1;
246             if (keyStart != -1) {
247                 int keyEnd = propertyName.indexOf(PROPERTY_KEY_SUFFIX, keyStart + PROPERTY_KEY_PREFIX.length());
248                 if (keyEnd != -1) {
249                     if (actualName == null) {
250                         actualName = propertyName.substring(0, keyStart);
251                     }
252                     String key = propertyName.substring(keyStart + PROPERTY_KEY_PREFIX.length(), keyEnd);
253                     if ((key.startsWith("'") && key.endsWith("'")) || (key.startsWith("\"") && key.endsWith("\""))) {
254                         key = key.substring(1, key.length() - 1);
255                     }
256                     keys.add(key);
257                     searchIndex = keyEnd + PROPERTY_KEY_SUFFIX.length();
258                 }
259             }
260         }
261         tokens.actualName = (actualName != null ? actualName : propertyName);
262         tokens.canonicalName = tokens.actualName;
263         if (!keys.isEmpty()) {
264             tokens.canonicalName += PROPERTY_KEY_PREFIX +
265                     StringUtils.collectionToDelimitedString(keys, PROPERTY_KEY_SUFFIX + PROPERTY_KEY_PREFIX) +
266                     PROPERTY_KEY_SUFFIX;
267             tokens.keys = StringUtils.toStringArray(keys);
268         }
269         return tokens;
270     }
271 
272     private static class PropertyTokenHolder {
273 
274         public String canonicalName;
275 
276         public String actualName;
277 
278         public String[] keys;
279     }
280 }