001: /*
002: * Copyright 2005-2007 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.opensource.org/licenses/ecl1.php
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.kuali.core.datadictionary;
017:
018: import java.beans.IntrospectionException;
019: import java.beans.PropertyDescriptor;
020: import java.util.Collection;
021: import java.util.HashMap;
022: import java.util.Map;
023: import java.util.TreeMap;
024:
025: import org.apache.commons.lang.StringUtils;
026: import org.kuali.core.bo.BusinessObject;
027: import org.kuali.core.bo.PersistableBusinessObjectExtension;
028: import org.kuali.core.datadictionary.exception.AttributeValidationException;
029: import org.kuali.core.datadictionary.exception.CompletionException;
030: import org.kuali.core.document.Document;
031: import org.kuali.core.maintenance.Maintainable;
032: import org.kuali.core.rule.PreRulesCheck;
033: import org.kuali.core.service.PersistenceStructureService;
034: import org.kuali.core.util.ObjectUtils;
035:
036: public class ValidationCompletionUtils {
037: //private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ValidationCompletionUtils.class);
038: private PersistenceStructureService persistenceStructureService;
039:
040: public ValidationCompletionUtils() {
041: }
042:
043: public void setPersistenceStructureService(
044: PersistenceStructureService persistenceStructureService) {
045: this .persistenceStructureService = persistenceStructureService;
046: }
047:
048: /**
049: * @param clazz
050: * @return true if the given Class is a descendent of BusinessObject
051: */
052: public boolean isBusinessObjectClass(Class clazz) {
053: return isDescendentClass(clazz, BusinessObject.class);
054: }
055:
056: /**
057: * @param clazz
058: * @return true if the given Class is a descendent of Maintainable
059: */
060: public boolean isMaintainableClass(Class clazz) {
061: return isDescendentClass(clazz, Maintainable.class);
062: }
063:
064: /**
065: * @param clazz
066: * @return true if the given Class is a descendent of Document
067: */
068: public boolean isDocumentClass(Class clazz) {
069: return isDescendentClass(clazz, Document.class);
070: }
071:
072: /**
073: * @param clazz
074: * @return true if the given Class is a descendent of PreRulesCheck
075: */
076: public boolean isPreRulesCheckClass(Class clazz) {
077: return isDescendentClass(clazz, PreRulesCheck.class);
078: }
079:
080: /**
081: * @param descendentClass
082: * @param ancestorClass
083: * @return true if descendentClass is a descendent of ancestorClass
084: */
085: public boolean isDescendentClass(Class descendentClass,
086: Class ancestorClass) {
087: if (descendentClass == null) {
088: throw new IllegalArgumentException(
089: "invalid (null) descendentClass");
090: } else if (ancestorClass == null) {
091: throw new IllegalArgumentException(
092: "invalid (null) ancestorClass");
093: }
094:
095: return ancestorClass.isAssignableFrom(descendentClass);
096: }
097:
098: /**
099: * @param clazz
100: * @param propertyName
101: * @return true if the given propertyName names a property of the given class
102: * @throws CompletionException if there is a problem accessing the named property on the given class
103: */
104: public boolean isPropertyOf(Class targetClass, String propertyName) {
105: if (targetClass == null) {
106: throw new IllegalArgumentException(
107: "invalid (null) targetClass");
108: }
109: if (StringUtils.isBlank(propertyName)) {
110: throw new IllegalArgumentException(
111: "invalid (blank) propertyName");
112: }
113:
114: PropertyDescriptor propertyDescriptor = buildReadDescriptor(
115: targetClass, propertyName);
116:
117: boolean isPropertyOf = (propertyDescriptor != null);
118: return isPropertyOf;
119: }
120:
121: /**
122: * @param clazz
123: * @param propertyName
124: * @return true if the given propertyName names a Collection property of the given class
125: * @throws CompletionException if there is a problem accessing the named property on the given class
126: */
127: public boolean isCollectionPropertyOf(Class targetClass,
128: String propertyName) {
129: boolean isCollectionPropertyOf = false;
130:
131: PropertyDescriptor propertyDescriptor = buildReadDescriptor(
132: targetClass, propertyName);
133: if (propertyDescriptor != null) {
134: Class clazz = propertyDescriptor.getPropertyType();
135:
136: if ((clazz != null)
137: && Collection.class.isAssignableFrom(clazz)) {
138: isCollectionPropertyOf = true;
139: }
140: }
141:
142: return isCollectionPropertyOf;
143: }
144:
145: /**
146: * This method determines the Class of the attributeName passed in. Null will be returned if the member is not available, or if
147: * a reflection exception is thrown.
148: *
149: * @param rootClass - Class that the attributeName property exists in.
150: * @param attributeName - Name of the attribute you want a class for.
151: * @return The Class of the attributeName, if the attribute exists on the rootClass. Null otherwise.
152: */
153: public Class getAttributeClass(Class boClass, String attributeName) {
154:
155: Class attributeClass = null;
156:
157: // fail loudly if the attributeName isnt a member of rootClass
158: if (!isPropertyOf(boClass, attributeName)) {
159: throw new AttributeValidationException(
160: "unable to find attribute '" + attributeName
161: + "' in rootClass '" + boClass.getName()
162: + "'");
163: }
164:
165: BusinessObject boInstance;
166: try {
167: boInstance = (BusinessObject) boClass.newInstance();
168: } catch (Exception e) {
169: throw new RuntimeException(e);
170: }
171:
172: // attempt to retrieve the class of the property
173: try {
174: attributeClass = ObjectUtils.getPropertyType(boInstance,
175: attributeName, persistenceStructureService);
176: } catch (Exception e) {
177: throw new RuntimeException(e);
178: }
179:
180: return attributeClass;
181: }
182:
183: /**
184: * This method determines the Class of the elements in the collectionName passed in.
185: *
186: * @param boClass Class that the collectionName collection exists in.
187: * @param collectionName the name of the collection you want the element class for
188: * @return
189: */
190: public Class getCollectionElementClass(Class boClass,
191: String collectionName) {
192: if (boClass == null) {
193: throw new IllegalArgumentException("invalid (null) boClass");
194: }
195: if (StringUtils.isBlank(collectionName)) {
196: throw new IllegalArgumentException(
197: "invalid (blank) collectionName");
198: }
199:
200: PropertyDescriptor propertyDescriptor = null;
201:
202: String[] intermediateProperties = collectionName.split("\\.");
203: Class currentClass = boClass;
204:
205: for (int i = 0; i < intermediateProperties.length; ++i) {
206:
207: String currentPropertyName = intermediateProperties[i];
208: propertyDescriptor = buildSimpleReadDescriptor(
209: currentClass, currentPropertyName);
210:
211: if (propertyDescriptor != null) {
212:
213: Class type = propertyDescriptor.getPropertyType();
214: if (Collection.class.isAssignableFrom(type)) {
215:
216: if (persistenceStructureService
217: .isPersistable(currentClass)) {
218:
219: Map<String, Class> collectionClasses = new HashMap<String, Class>();
220: collectionClasses = persistenceStructureService
221: .listCollectionObjectTypes(currentClass);
222: currentClass = collectionClasses
223: .get(currentPropertyName);
224:
225: } else {
226: throw new RuntimeException(
227: "Can't determine the Class of Collection elements because persistenceStructureService.isPersistable("
228: + currentClass.getName()
229: + ") returns false.");
230: }
231:
232: } else {
233:
234: currentClass = propertyDescriptor.getPropertyType();
235:
236: }
237: }
238: }
239:
240: return currentClass;
241: }
242:
243: static private Map<String, Map<String, PropertyDescriptor>> cache = new TreeMap<String, Map<String, PropertyDescriptor>>();
244:
245: /**
246: * @param propertyClass
247: * @param propertyName
248: * @return PropertyDescriptor for the getter for the named property of the given class, if one exists.
249: */
250: private PropertyDescriptor buildReadDescriptor(Class propertyClass,
251: String propertyName) {
252: if (propertyClass == null) {
253: throw new IllegalArgumentException(
254: "invalid (null) propertyClass");
255: }
256: if (StringUtils.isBlank(propertyName)) {
257: throw new IllegalArgumentException(
258: "invalid (blank) propertyName");
259: }
260:
261: PropertyDescriptor propertyDescriptor = null;
262:
263: String[] intermediateProperties = propertyName.split("\\.");
264: int lastLevel = intermediateProperties.length - 1;
265: Class currentClass = propertyClass;
266:
267: for (int i = 0; i <= lastLevel; ++i) {
268:
269: String currentPropertyName = intermediateProperties[i];
270: propertyDescriptor = buildSimpleReadDescriptor(
271: currentClass, currentPropertyName);
272:
273: if (i < lastLevel) {
274:
275: if (propertyDescriptor != null) {
276:
277: Class propertyType = propertyDescriptor
278: .getPropertyType();
279: if (propertyType
280: .equals(PersistableBusinessObjectExtension.class)) {
281: propertyType = persistenceStructureService
282: .getBusinessObjectAttributeClass(
283: currentClass,
284: currentPropertyName);
285: }
286: if (Collection.class.isAssignableFrom(propertyType)) {
287:
288: if (persistenceStructureService
289: .isPersistable(currentClass)) {
290:
291: Map<String, Class> collectionClasses = new HashMap<String, Class>();
292: collectionClasses = persistenceStructureService
293: .listCollectionObjectTypes(currentClass);
294: currentClass = collectionClasses
295: .get(currentPropertyName);
296:
297: } else {
298:
299: throw new RuntimeException(
300: "Can't determine the Class of Collection elements because persistenceStructureService.isPersistable("
301: + currentClass.getName()
302: + ") returns false.");
303:
304: }
305:
306: } else {
307:
308: currentClass = propertyType;
309:
310: }
311:
312: }
313:
314: }
315:
316: }
317:
318: return propertyDescriptor;
319: }
320:
321: /**
322: * @param propertyClass
323: * @param propertyName
324: * @return PropertyDescriptor for the getter for the named property of the given class, if one exists.
325: */
326: private PropertyDescriptor buildSimpleReadDescriptor(
327: Class propertyClass, String propertyName) {
328: if (propertyClass == null) {
329: throw new IllegalArgumentException(
330: "invalid (null) propertyClass");
331: }
332: if (StringUtils.isBlank(propertyName)) {
333: throw new IllegalArgumentException(
334: "invalid (blank) propertyName");
335: }
336:
337: PropertyDescriptor p = null;
338:
339: // check to see if we've cached this descriptor already. if yes, return true.
340: String propertyClassName = propertyClass.getName();
341: Map<String, PropertyDescriptor> m = cache
342: .get(propertyClassName);
343: if (null != m) {
344: p = m.get(propertyName);
345: if (null != p) {
346: return p;
347: }
348: }
349:
350: String prefix = StringUtils.capitalize(propertyName);
351: String getName = "get" + prefix;
352: String isName = "is" + prefix;
353:
354: try {
355:
356: p = new PropertyDescriptor(propertyName, propertyClass,
357: getName, null);
358:
359: } catch (IntrospectionException e) {
360: try {
361:
362: p = new PropertyDescriptor(propertyName, propertyClass,
363: isName, null);
364:
365: } catch (IntrospectionException f) {
366: // ignore it
367: }
368: }
369:
370: // cache the property descriptor if we found it.
371: if (null != p) {
372:
373: if (null == m) {
374:
375: m = new TreeMap<String, PropertyDescriptor>();
376: cache.put(propertyClassName, m);
377:
378: }
379:
380: m.put(propertyName, p);
381:
382: }
383:
384: return p;
385: }
386: }
|