View Javadoc

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