1 /**
2 * Copyright 2005-2012 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 }