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.util;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.krad.uif.field.DataField;
20  import org.kuali.rice.krad.uif.view.View;
21  import org.springframework.beans.PropertyValues;
22  import org.springframework.beans.factory.config.TypedStringValue;
23  
24  import java.util.Collection;
25  import java.util.Map;
26  
27  /**
28   * Provides methods for getting property values, types, and paths within the
29   * context of a <code>View</code>
30   *
31   * <p>
32   * The view provides a special map named 'abstractTypeClasses' that indicates
33   * concrete classes that should be used in place of abstract property types that
34   * are encountered on the object graph. This classes takes into account that map
35   * while dealing with properties. e.g. suppose we have propertyPath
36   * 'document.name' on the form, with the type of the document property set to
37   * the interface Document. Using class introspection we would get back the
38   * interface type for document and this would not be able to get the property
39   * type for name. Using the view map, we can replace document with a concrete
40   * class and then use it to get the name property
41   * </p>
42   *
43   * @author Kuali Rice Team (rice.collab@kuali.org)
44   */
45  public class ViewModelUtils {
46  
47      /**
48       * Determines the associated type for the property within the View context
49       *
50       * <p>
51       * Property path is full path to property from the View Form class. The abstract type classes
52       * map configured on the View will be consulted for any entries that match the property path. If the
53       * property path given contains a partial match to an abstract class (somewhere on path is an abstract
54       * class), the property type will be retrieved based on the given concrete class to use and the part
55       * of the path remaining. If no matching entry is found, standard reflection is used to get the type
56       * </p>
57       *
58       * @param view - view instance providing the context (abstract map)
59       * @param propertyPath - full path to property to retrieve type for (relative to the form class)
60       * @return Class<?> type of property in model, or Null if type could not be determined
61       * @see org.kuali.rice.krad.uif.view.View#getAbstractTypeClasses()
62       */
63      public static Class<?> getPropertyTypeByClassAndView(View view, String propertyPath) {
64          Class<?> propertyType = null;
65  
66          if (StringUtils.isBlank(propertyPath)) {
67              return propertyType;
68          }
69  
70          // in case of partial match, holds the class that matched and the
71          // property so we can get by reflection
72          Class<?> modelClass = view.getFormClass();
73          String modelProperty = propertyPath;
74  
75          int bestMatchLength = 0;
76  
77          // removed collection indexes from path for matching
78          String flattenedPropertyPath = propertyPath.replaceAll("\\[.+\\]", "");
79  
80          // check if property path matches one of the modelClass entries
81          Map<String, Class<?>> modelClasses = view.getAbstractTypeClasses();
82          for (String path : modelClasses.keySet()) {
83              // full match
84              if (StringUtils.equals(path, flattenedPropertyPath)) {
85                  propertyType = modelClasses.get(path);
86                  break;
87              }
88  
89              // partial match
90              if (flattenedPropertyPath.startsWith(path) && (path.length() > bestMatchLength)) {
91                  bestMatchLength = path.length();
92  
93                  modelClass = modelClasses.get(path);
94                  modelProperty = StringUtils.removeStart(flattenedPropertyPath, path);
95                  modelProperty = StringUtils.removeStart(modelProperty, ".");
96              }
97          }
98  
99          // if full match not found, get type based on reflection
100         if (propertyType == null) {
101             propertyType = ObjectPropertyUtils.getPropertyType(modelClass, modelProperty);
102         }
103 
104         return propertyType;
105     }
106 
107     public static String getParentObjectPath(DataField field) {
108         String parentObjectPath = "";
109 
110         String objectPath = field.getBindingInfo().getBindingObjectPath();
111         String propertyPrefix = field.getBindingInfo().getBindByNamePrefix();
112 
113         if (!field.getBindingInfo().isBindToForm() && StringUtils.isNotBlank(objectPath)) {
114             parentObjectPath = objectPath;
115         }
116 
117         if (StringUtils.isNotBlank(propertyPrefix)) {
118             if (StringUtils.isNotBlank(parentObjectPath)) {
119                 parentObjectPath += ".";
120             }
121 
122             parentObjectPath += propertyPrefix;
123         }
124 
125         return parentObjectPath;
126     }
127 
128     public static Class<?> getParentObjectClassForMetadata(View view, DataField field) {
129         String parentObjectPath = getParentObjectPath(field);
130 
131         return getPropertyTypeByClassAndView(view, parentObjectPath);
132     }
133 
134     public static Class<?> getParentObjectClassForMetadata(View view, Object model, DataField field) {
135         String parentObjectPath = getParentObjectPath(field);
136 
137         return getObjectClassForMetadata(view, model, parentObjectPath);
138     }
139 
140     public static Class<?> getObjectClassForMetadata(View view, Object model, String propertyPath) {
141         // get class by object instance if not null
142         Object parentObject = ObjectPropertyUtils.getPropertyValue(model, propertyPath);
143         if (parentObject != null) {
144             return parentObject.getClass();
145         }
146 
147         // get class by property type with abstract map check
148         return getPropertyTypeByClassAndView(view, propertyPath);
149     }
150 
151     public static Object getParentObjectForMetadata(View view, Object model, DataField field) {
152         // default to model as parent
153         Object parentObject = model;
154 
155         String parentObjectPath = getParentObjectPath(field);
156         if (StringUtils.isNotBlank(parentObjectPath)) {
157             parentObject = ObjectPropertyUtils.getPropertyValue(model, parentObjectPath);
158 
159             // attempt to create new instance if parent is null or is a
160             // collection or map
161             if ((parentObject == null) || Collection.class.isAssignableFrom(parentObject.getClass()) ||
162                     Map.class.isAssignableFrom(parentObject.getClass())) {
163                 try {
164                     Class<?> parentObjectClass = getPropertyTypeByClassAndView(view, parentObjectPath);
165                     if (parentObjectClass != null) {
166                         parentObject = parentObjectClass.newInstance();
167                     }
168                 } catch (InstantiationException e) {
169                     // swallow exception and let null be returned
170                 } catch (IllegalAccessException e) {
171                     // swallow exception and let null be returned
172                 }
173             }
174         }
175 
176         return parentObject;
177     }
178 
179     /**
180      * Helper method for getting the string value of a property from a {@link PropertyValues}
181      *
182      * @param propertyValues - property values instance to pull from
183      * @param propertyName - name of property whose value should be retrieved
184      * @return String value for property or null if property was not found
185      */
186     public static String getStringValFromPVs(PropertyValues propertyValues, String propertyName) {
187         String propertyValue = null;
188 
189         if ((propertyValues != null) && propertyValues.contains(propertyName)) {
190             Object pvValue = propertyValues.getPropertyValue(propertyName).getValue();
191             if (pvValue instanceof TypedStringValue) {
192                 TypedStringValue typedStringValue = (TypedStringValue) pvValue;
193                 propertyValue = typedStringValue.getValue();
194             } else if (pvValue instanceof String) {
195                 propertyValue = (String) pvValue;
196             }
197         }
198 
199         return propertyValue;
200     }
201 }