View Javadoc
1   /**
2    * Copyright 2005-2016 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.uif.lifecycle.initialize;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.krad.datadictionary.AttributeDefinition;
20  import org.kuali.rice.krad.service.DataDictionaryService;
21  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
22  import org.kuali.rice.krad.uif.UifConstants;
23  import org.kuali.rice.krad.uif.UifPropertyPaths;
24  import org.kuali.rice.krad.uif.component.BindingInfo;
25  import org.kuali.rice.krad.uif.field.DataField;
26  import org.kuali.rice.krad.uif.field.InputField;
27  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
28  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleTaskBase;
29  import org.kuali.rice.krad.uif.util.ComponentFactory;
30  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
31  import org.kuali.rice.krad.uif.view.ExpressionEvaluator;
32  import org.kuali.rice.krad.uif.view.View;
33  import org.kuali.rice.krad.util.KRADConstants;
34  
35  import java.util.List;
36  import java.util.Map;
37  import java.util.Map.Entry;
38  
39  /**
40   * Performs initialization on data fields based on attributes found in the data dictionary.
41   * 
42   * @author Kuali Rice Team (rice.collab@kuali.org)
43   */
44  public class InitializeDataFieldFromDictionaryTask extends ViewLifecycleTaskBase<DataField> {
45  
46      /**
47       * Constructor.
48       * 
49       * @param phase The initialize phase for the data field.
50       */
51      public InitializeDataFieldFromDictionaryTask() {
52          super(DataField.class);
53      }
54  
55      /**
56       * Sets properties of the <code>InputField</code> (if blank) to the corresponding attribute
57       * entry in the data dictionary
58       *
59       * {@inheritDoc}
60       */
61      @Override
62      protected void performLifecycleTask() {
63          DataField field = (DataField) getElementState().getElement();
64  
65          AttributeDefinition attributeDefinition = null;
66  
67          String dictionaryAttributeName = field.getDictionaryAttributeName();
68          String dictionaryObjectEntry = field.getDictionaryObjectEntry();
69  
70          Map<String, String> propertyExpressions = field.getPropertyExpressions();
71  
72          if (dictionaryAttributeName == null && propertyExpressions.containsKey(UifPropertyPaths.DICTIONARY_ATTR_NAME)) {
73              ExpressionEvaluator expressionEvaluator = ViewLifecycle.getExpressionEvaluator();
74              dictionaryAttributeName = propertyExpressions.get(UifPropertyPaths.DICTIONARY_ATTR_NAME);
75  
76              if (dictionaryAttributeName.contains(UifConstants.DEFAULT_PATH_BIND_ADJUST_PREFIX)) {
77                  dictionaryAttributeName = dictionaryAttributeName.replace(UifConstants.DEFAULT_PATH_BIND_ADJUST_PREFIX,
78                          "");
79              } else if (dictionaryAttributeName.contains(UifConstants.LINE_PATH_BIND_ADJUST_PREFIX)) {
80                  // It is not possible to add both the collection and index to the context at this time.  Until
81                  // the dictionaryAttributeName expression can be properly evaluated, get the attribute definition for
82                  // the field by looking at the first item in the collection if an item exists.
83  
84                  List<Object> collection = ObjectPropertyUtils.getPropertyValue(ViewLifecycle.getModel(),
85                         field.getBindingInfo().getCollectionPath());
86  
87                  if (!collection.isEmpty()) {
88                      dictionaryAttributeName = dictionaryAttributeName.replace(UifConstants.LINE_PATH_BIND_ADJUST_PREFIX,
89                              field.getBindingInfo().getCollectionPath() + "[0].");
90                  } else {
91                      dictionaryAttributeName = null;
92                  }
93              }
94  
95              dictionaryAttributeName = (String) expressionEvaluator.evaluateExpression(field.getContext(),
96                      dictionaryAttributeName);
97          }
98  
99          if (!(dictionaryAttributeName == null && propertyExpressions.containsKey(UifPropertyPaths.DICTIONARY_ATTR_NAME))) {
100 
101             // if entry given but not attribute name, use field name as attribute name
102             if (StringUtils.isNotBlank(dictionaryObjectEntry) && StringUtils.isBlank(dictionaryAttributeName)) {
103                 dictionaryAttributeName = field.getPropertyName();
104             }
105 
106             // if dictionary entry and attribute set, attempt to find definition
107             if (StringUtils.isNotBlank(dictionaryAttributeName) && StringUtils.isNotBlank(dictionaryObjectEntry)) {
108                 attributeDefinition = KRADServiceLocatorWeb.getDataDictionaryService().getAttributeDefinition(
109                         dictionaryObjectEntry, dictionaryAttributeName);
110             }
111 
112             // if definition not found, recurse through path
113             if (attributeDefinition == null) {
114                 BindingInfo fieldBindingInfo = field.getBindingInfo();
115                 String collectionPath = fieldBindingInfo.getCollectionPath();
116 
117                 String propertyPath;
118                 if (StringUtils.isNotBlank(collectionPath)) {
119                     StringBuilder propertyPathBuilder = new StringBuilder();
120 
121                     String bindingObjectPath = fieldBindingInfo.getBindingObjectPath();
122                     if (StringUtils.isNotBlank(bindingObjectPath)) {
123                         propertyPathBuilder.append(bindingObjectPath).append('.');
124                     }
125 
126                     propertyPathBuilder.append(collectionPath).append('.');
127 
128                     String bindByNamePrefix = fieldBindingInfo.getBindByNamePrefix();
129                     if (StringUtils.isNotBlank(bindByNamePrefix)) {
130 
131                         // fix for both collectionPath and bindByNamePrefix being set,
132                         // wherein the bindByNamePrefix contains the collectionPath
133                         if (!bindByNamePrefix.startsWith(collectionPath)) {
134                             propertyPathBuilder.append(bindByNamePrefix).append('.');
135                         }
136                     }
137 
138                     propertyPathBuilder.append(fieldBindingInfo.getBindingName());
139                     propertyPath = propertyPathBuilder.toString();
140                 } else {
141                     propertyPath = field.getBindingInfo().getBindingPath();
142                 }
143 
144                 attributeDefinition = findNestedDictionaryAttribute(propertyPath);
145             }
146 
147             // if a definition was found, initialize field from definition
148             if (attributeDefinition != null) {
149                 field.copyFromAttributeDefinition(attributeDefinition);
150             }
151         }
152 
153         // if control still null, assign default
154         if (field instanceof InputField) {
155             InputField inputField = (InputField) field;
156             if (inputField.getControl() == null) {
157                 inputField.setControl(ComponentFactory.getTextControl());
158             }
159         }
160     }
161 
162     /**
163      * Determines the name of a data dictionary entry based on the portion of the path leading up to
164      * the attribute name.
165      * 
166      * <p>
167      * The property path passed in is checked first against
168      * {@link View#getObjectPathToConcreteClassMapping()} for a full or partial match. If no match
169      * is found then property type relative to the model involved in the current view lifecycle is
170      * returned, where applicable.
171      * </p>
172      * 
173      * @param dictionaryEntryPrefix Portion of a property path referring to the entry that has the
174      *        attribute.
175      * @return The name of the dictionary entry indicated by the property path.
176      */
177     private String getDictionaryEntryName(String dictionaryEntryPrefix) {
178         if (StringUtils.isEmpty(dictionaryEntryPrefix)) {
179             return dictionaryEntryPrefix;
180         }
181         
182         Map<String, Class<?>> modelClasses = ViewLifecycle.getView().getObjectPathToConcreteClassMapping();
183         Class<?> dictionaryModelClass = modelClasses.get(dictionaryEntryPrefix);
184 
185         // full match
186         if (dictionaryModelClass != null) {
187             return dictionaryModelClass.getName();
188         }
189 
190         // in case of partial match, holds the class that matched and the
191         // property so we can get by reflection
192         Class<?> modelClass = null;
193         String modelProperty = dictionaryEntryPrefix;
194 
195         int bestMatchLength = 0;
196         int modelClassPathLength = dictionaryEntryPrefix.length();
197 
198         // check if property path matches one of the modelClass entries
199         synchronized (modelClasses) {
200             // synchronizing on modelClasses prevents ConcurrentModificationException during
201             // asynchronous lifecycle processing
202             for (Entry<String, Class<?>> modelClassEntry : modelClasses.entrySet()) {
203                 String path = modelClassEntry.getKey();
204                 int pathlen = path.length();
205 
206                 if (dictionaryEntryPrefix.startsWith(path) && pathlen > bestMatchLength
207                         && modelClassPathLength > pathlen && dictionaryEntryPrefix.charAt(pathlen) == '.') {
208                     bestMatchLength = pathlen;
209                     modelClass = modelClassEntry.getValue();
210                     modelProperty = dictionaryEntryPrefix.substring(pathlen + 1);
211                 }
212             }
213         }
214 
215         if (modelClass != null) {
216             // if a partial match was found, look up the property type based on matched model class
217             dictionaryModelClass = ObjectPropertyUtils.getPropertyType(modelClass, modelProperty);
218         }
219 
220         if (dictionaryModelClass == null) {
221             // If no full or partial match, look up based on the model directly
222             dictionaryModelClass = ObjectPropertyUtils.getPropertyType(ViewLifecycle.getModel(), dictionaryEntryPrefix);
223         }
224 
225         return dictionaryModelClass == null ? null : dictionaryModelClass.getName();
226     }
227 
228     /**
229      * Recursively drills down the property path (if nested) to find an AttributeDefinition, the
230      * first attribute definition found will be returned
231      * 
232      * <p>
233      * e.g. suppose parentPath is 'document' and propertyPath is 'account.subAccount.name', first
234      * the property type for document will be retrieved using the view metadata and used as the
235      * dictionary entry, with the propertyPath as the dictionary attribute, if an attribute
236      * definition exists it will be returned. Else, the first part of the property path is added to
237      * the parent, making the parentPath 'document.account' and the propertyPath 'subAccount.name',
238      * the method is then called again to perform the process with those parameters. The recursion
239      * continues until an attribute field is found, or the propertyPath is no longer nested
240      * </p>
241      * 
242      * @param propertyPath path of the property to use as dictionary attribute and to drill down on
243      * @return AttributeDefinition if found, or Null
244      */
245     protected AttributeDefinition findNestedDictionaryAttribute(String propertyPath) {
246         DataField field = (DataField) getElementState().getElement();
247 
248         String fieldBindingPrefix = null;
249         String dictionaryAttributePath = propertyPath;
250 
251         if (field.getBindingInfo().isBindToMap()) {
252             fieldBindingPrefix = "";
253             if (!field.getBindingInfo().isBindToForm() && StringUtils.isNotBlank(
254                     field.getBindingInfo().getBindingObjectPath())) {
255                 fieldBindingPrefix = field.getBindingInfo().getBindingObjectPath();
256             }
257             if (StringUtils.isNotBlank(field.getBindingInfo().getBindByNamePrefix())) {
258                 if (StringUtils.isNotBlank(fieldBindingPrefix)) {
259                     fieldBindingPrefix += "." + field.getBindingInfo().getBindByNamePrefix();
260                 } else {
261                     fieldBindingPrefix = field.getBindingInfo().getBindByNamePrefix();
262                 }
263             }
264 
265             dictionaryAttributePath = field.getBindingInfo().getBindingName();
266         }
267 
268         if (StringUtils.isEmpty(dictionaryAttributePath)) {
269             return null;
270         }
271 
272         if (StringUtils.startsWith(dictionaryAttributePath, KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX)) {
273             dictionaryAttributePath = StringUtils.substringAfter(dictionaryAttributePath, KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX);
274         }
275         
276         DataDictionaryService ddService = KRADServiceLocatorWeb.getDataDictionaryService();
277         
278         String dictionaryAttributeName = ObjectPropertyUtils.getCanonicalPath(dictionaryAttributePath);
279         String dictionaryEntryPrefix = fieldBindingPrefix;
280         
281         AttributeDefinition attribute = null;
282         String dictionaryEntryName = null;
283         
284         int i = dictionaryAttributeName.indexOf('.');
285         while (attribute == null && i != -1) {
286             
287             if (dictionaryEntryPrefix != null) {
288                 dictionaryEntryName = getDictionaryEntryName(dictionaryEntryPrefix);
289                 dictionaryEntryPrefix += '.' + dictionaryAttributeName.substring(0, i);
290             } else {
291                 dictionaryEntryName = null;
292                 dictionaryEntryPrefix = dictionaryAttributeName.substring(0, i);
293             }
294 
295             if (dictionaryEntryName != null) {
296                 attribute = ddService.getAttributeDefinition(dictionaryEntryName, dictionaryAttributeName);
297             }
298             
299             if (attribute == null) {
300                 dictionaryAttributeName = dictionaryAttributeName.substring(i+1);
301                 i = dictionaryAttributeName.indexOf('.');
302             }
303         }
304         
305         if (attribute == null && dictionaryEntryPrefix != null) {
306             dictionaryEntryName = getDictionaryEntryName(dictionaryEntryPrefix);
307             
308             if (dictionaryEntryName != null) {
309                 attribute = ddService.getAttributeDefinition(dictionaryEntryName, dictionaryAttributeName);
310             }
311         }
312         
313         // if a definition was found, update the fields dictionary properties
314         if (attribute != null) {
315             field.setDictionaryObjectEntry(dictionaryEntryName);
316             field.setDictionaryAttributeName(dictionaryAttributeName);
317         }
318 
319         return attribute;
320     }
321 
322 }