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