001: /*
002: * Copyright 2006-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.service.impl;
017:
018: import java.lang.reflect.Constructor;
019: import java.lang.reflect.Field;
020: import java.lang.reflect.InvocationTargetException;
021: import java.sql.Connection;
022: import java.sql.DatabaseMetaData;
023: import java.sql.ResultSet;
024: import java.sql.SQLException;
025: import java.sql.Timestamp;
026: import java.util.Iterator;
027: import java.util.Map;
028: import java.util.Vector;
029:
030: import org.apache.commons.beanutils.PropertyUtils;
031: import org.apache.commons.lang.StringUtils;
032: import org.apache.ojb.broker.PersistenceBroker;
033: import org.apache.ojb.broker.PersistenceBrokerFactory;
034: import org.apache.ojb.broker.accesslayer.LookupException;
035: import org.apache.ojb.broker.core.proxy.ProxyHelper;
036: import org.apache.ojb.broker.metadata.ClassDescriptor;
037: import org.apache.ojb.broker.metadata.FieldDescriptor;
038: import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
039: import org.kuali.core.exceptions.IntrospectionException;
040: import org.kuali.core.exceptions.MetadataException;
041: import org.kuali.core.util.ObjectUtils;
042:
043: /**
044: * PersistenceService methods that are only used for testing have been moved into this class.
045: */
046: public class PersistenceTestServiceImpl extends
047: PersistenceServiceImplBase {
048:
049: /**
050: * @see org.kuali.core.service.PersistenceService#getColumnMetadata(java.lang.Class, java.lang.String)
051: */
052: public int getColumnSize(Class persistableClass,
053: String attributeName) {
054: if (persistableClass == null) {
055: throw new IllegalArgumentException(
056: "invalid (null) persistableClass");
057: }
058: if (StringUtils.isBlank(attributeName)) {
059: throw new IllegalArgumentException(
060: "invalid (blank) attributeName");
061: }
062:
063: // find table and column for given class and attribute
064: ClassDescriptor classDescriptor = getClassDescriptor(persistableClass);
065: FieldDescriptor fieldDescriptor = classDescriptor
066: .getFieldDescriptorByName(attributeName);
067:
068: String tableName = getTableName(classDescriptor);
069: String columnName = fieldDescriptor.getColumnName();
070:
071: int columnSize = 0;
072:
073: PersistenceBroker broker = null;
074:
075: try {
076: broker = PersistenceBrokerFactory
077: .defaultPersistenceBroker();
078: Connection connection = broker.serviceConnectionManager()
079: .getConnection();
080: DatabaseMetaData databaseMetadata = connection
081: .getMetaData();
082: ResultSet rs = databaseMetadata.getColumns(null, null,
083: tableName, columnName);
084: if (rs.next()) {
085: columnSize = new Integer(rs.getInt("COLUMN_SIZE"));
086: }
087: rs.close();
088: } catch (LookupException e) {
089: throw new MetadataException(
090: "unable to get database connection", e);
091: } catch (SQLException e) {
092: throw new MetadataException(
093: "error retrieving column metadata for '"
094: + persistableClass.getName() + "."
095: + attributeName + "'", e);
096: }
097:
098: return columnSize;
099: }
100:
101: /**
102: * populates a given business object with arbitrary values
103: *
104: * @param persistableObject object whose attributes need to be populated based on its ojb mapping
105: * @param ojbMappedAttributes a map to store the names of the properties that were set in, which can be accessed by the calling
106: * class without exposing ojb related mappings data directly
107: */
108: public void populateBusinessObjectForTesting(
109: Object persistableObject, Map ojbMappedAttributes)
110: throws Exception {
111: if (ojbMappedAttributes == null) {
112: throw new IllegalArgumentException("invalid (null) Map");
113: }
114: if (persistableObject == null) {
115: throw new IllegalArgumentException("invalid (null) object");
116: }
117: Object[] keyValuePair = null;
118: ClassDescriptor classDescriptor = getClassDescriptor(persistableObject
119: .getClass());
120: int tmpValue = 0;
121: String className = null;
122: String fieldName = null;
123: try {
124: // populate all non reference/anonymous attributes
125: FieldDescriptor[] persistableField = classDescriptor
126: .getFieldDescriptions();
127: Object fieldValue = null;
128: for (int i = 0; i < persistableField.length; i++) {
129: if (!persistableField[i].isAnonymous()) {
130: fieldValue = Integer.toString(tmpValue++);
131: keyValuePair = forceFieldAssignment(
132: persistableObject, persistableField[i],
133: fieldValue);
134: if (null != keyValuePair) {
135: ojbMappedAttributes.put(keyValuePair[0],
136: keyValuePair[1]);
137: }
138: }
139: }
140: // iterate through all object references for the persistableObject
141: Vector objectReferences = classDescriptor
142: .getObjectReferenceDescriptors();
143: for (Iterator iter = objectReferences.iterator(); iter
144: .hasNext();) {
145: ObjectReferenceDescriptor referenceDescriptor = (ObjectReferenceDescriptor) iter
146: .next();
147:
148: // get the actual reference object
149: className = persistableObject.getClass().getName();
150: fieldName = referenceDescriptor.getAttributeName();
151: Object referenceObject = ObjectUtils.getPropertyValue(
152: persistableObject, fieldName);
153: if (referenceObject == null) {
154: referenceObject = referenceDescriptor
155: .getItemClass().newInstance();
156: ObjectUtils.setObjectProperty(persistableObject,
157: fieldName, referenceObject.getClass(),
158: referenceObject);
159: }
160:
161: // iterate through the keys for the reference object and set value
162: FieldDescriptor[] fieldDesc = referenceDescriptor
163: .getForeignKeyFieldDescriptors(classDescriptor);
164: Class refClass = ProxyHelper
165: .getRealClass(referenceObject);
166: ClassDescriptor refCld = getClassDescriptor(refClass);
167: FieldDescriptor[] refPkNames = refCld.getPkFields();
168: Map pkKeyValues = getPrimaryKeyFieldValues(referenceObject);
169:
170: for (int i = 0; i < fieldDesc.length; i++) {
171: FieldDescriptor fkField = fieldDesc[i];
172: String fkName = fkField.getAttributeName();
173:
174: // if the fk already has value for the object, skip
175: Object fkValue = null;
176: if (pkKeyValues.containsKey(fkName)) {
177: fkValue = pkKeyValues.get(fkName);
178: }
179: if (fkValue != null
180: && StringUtils.isNotBlank(fkValue
181: .toString())) {
182: continue;
183: }
184:
185: // if fk is not anonymous, get value from main object
186: if (!fkField.isAnonymous()) {
187: fieldName = fkName;
188: if (PropertyUtils.isReadable(persistableObject,
189: fkName)) {
190: fkValue = ObjectUtils.getPropertyValue(
191: persistableObject, fkName);
192: }
193: } else {
194: // find the value from one of the other reference objects
195: for (Iterator iter2 = objectReferences
196: .iterator(); iter2.hasNext();) {
197: ObjectReferenceDescriptor checkDescriptor = (ObjectReferenceDescriptor) iter2
198: .next();
199:
200: fkValue = getReferenceFKValue(
201: persistableObject, checkDescriptor,
202: fkName);
203: if (fkValue != null
204: && StringUtils.isNotBlank(fkValue
205: .toString())) {
206: break;
207: }
208: }
209: }
210:
211: // set the fk value
212: if (fkValue == null) {
213: fkValue = Integer.toString(tmpValue++);
214: }
215: // set values for ojb reference classes
216: keyValuePair = forceFieldAssignment(
217: referenceObject, refPkNames[i], fkValue);
218:
219: if (null != keyValuePair) {
220: if (fkField.isAnonymous()) {
221: keyValuePair[0] = fieldName + "."
222: + keyValuePair[0];
223: }
224: ojbMappedAttributes.put(keyValuePair[0],
225: keyValuePair[1]);
226: }
227: // set value for same field in parent .. if it exists
228: keyValuePair = forceFieldAssignment(
229: persistableObject, fkField, fkValue);
230: if (!fkField.isPrimaryKey() && null != keyValuePair) {
231: throw new IntrospectionException(
232: "field exists in both parent and child. not marked as anonoymous or primary key..bad mapping. parent:'"
233: + className
234: + "."
235: + keyValuePair[0]
236: + "' child:'"
237: + className
238: + "."
239: + fieldName
240: + "." + keyValuePair[0] + "'");
241: }
242: if (null != keyValuePair) {
243: ojbMappedAttributes.put(keyValuePair[0],
244: keyValuePair[1]);
245: }
246: }
247: }
248:
249: } catch (NoSuchMethodException e) {
250: throw new IntrospectionException("no setter for property '"
251: + className + "." + fieldName + "'", e);
252: } catch (IllegalAccessException e) {
253: throw new IntrospectionException(
254: "problem accessing property '" + className + "."
255: + fieldName + "'", e);
256: } catch (InvocationTargetException e) {
257: throw new IntrospectionException(
258: "problem invoking getter for property '"
259: + className + "." + fieldName + "'", e);
260: }
261: }
262:
263: /**
264: * used by <code>populateBusinessObjectForGenericJunitTest</code> to assign values to specified field of business object.
265: * reflection is used to 'force' assignment of a value to any datatypes that PropertyUtils cannot set on its own
266: *
267: * @param persistableObject
268: * @param persistableField
269: * @param fieldvalue
270: * @return
271: * @throws SecurityException
272: * @throws IllegalAccessException
273: * @throws InvocationTargetException
274: * @throws NoSuchMethodException
275: */
276: private Object[] forceFieldAssignment(Object persistableObject,
277: FieldDescriptor persistableField, Object fieldvalue)
278: throws SecurityException, IllegalAccessException,
279: InvocationTargetException, NoSuchMethodException {
280:
281: // replace OJB bean notation with struts
282: String fieldName = StringUtils.replace(persistableField
283: .getAttributeName(), "::", ".");
284: String columnName = persistableField.getColumnName();
285: Class fieldTypeClass = persistableField.getPersistentField()
286: .getType();
287: if (persistableField.isAnonymous() && null != fieldTypeClass) {
288: throw new IntrospectionException(
289: "Field declared as anonymous but exists in parent class '"
290: + persistableObject.getClass() + "."
291: + fieldName + "'");
292: }
293: Object setValue = null;
294: if (!persistableField.isAnonymous()
295: && null == ObjectUtils.getPropertyValue(
296: persistableObject, fieldName)
297: && (!StringUtils
298: .equalsIgnoreCase(columnName, "ver_nbr") && !StringUtils
299: .equalsIgnoreCase(columnName, "obj_id"))) {
300: try {
301: if (fieldvalue instanceof String) {
302: fieldvalue = StringUtils.substring(
303: (String) fieldvalue, 0, getColumnSize(
304: persistableObject.getClass(),
305: fieldName));
306: }
307:
308: ObjectUtils.setObjectProperty(persistableObject,
309: fieldName, fieldvalue.getClass(), fieldvalue);
310: setValue = ObjectUtils.getPropertyValue(
311: persistableObject, fieldName);
312: } catch (Throwable e1) {
313: // conventional setting of field failed so use reflection to force field
314: // assignment
315: Field[] fieldTypeClassFields = fieldTypeClass
316: .getFields();
317: // *NOTE* int.class MUST come before String.class in the array of
318: // <code>ValidConstructorParameterTypes</code>.
319: // specifically for <code>KualiDecimal</code> since kuali decimal has
320: // constructor for both types( int,String) but
321: // they behave differently. if the string constructor is used the value
322: // passed is taken as a literal, but will come
323: // back from the database with 2 decimal place precision in place. this
324: // will cause a not equal assertion error. (i.e
325: // used string value of '9' to construct a kuali decimal. kuali decimal
326: // value='9', when stored and later retrieved
327: // from database value would be '9.00'). if and int is passed to the
328: // constructor 2 digit decimal precision is done
329: // 'for free' (i.e. int value of '9' is passed to constructor. value is
330: // instance is '9.00', value from database is
331: // '9.00'), thus preventing an error of input != output
332: final Class[] validConstructorParameterTypes = {
333: int.class, long.class, String.class };
334: Class[] parameters = null;
335: Object[] parameter = null;
336: for (int j = 0; j < validConstructorParameterTypes.length; j++) {
337: try {
338: parameters = new Class[] { validConstructorParameterTypes[j] };
339: Constructor constructor = fieldTypeClass
340: .getDeclaredConstructor(parameters);
341:
342: if (validConstructorParameterTypes[j]
343: .equals(String.class)) {
344: parameter = new Object[] { (String) fieldvalue };
345: } else if (validConstructorParameterTypes[j]
346: .equals(int.class)) {
347: parameter = new Object[] { new Integer(
348: (String) fieldvalue) };
349: } else if (validConstructorParameterTypes[j]
350: .equals(long.class)) {
351: parameter = new Object[] { new Long(
352: (String) fieldvalue) };
353: }
354: fieldvalue = constructor.newInstance(parameter);
355: if (fieldTypeClass.equals(Timestamp.class)) {
356: // special case, must clear nano secods or assertions will fail
357: // due to loss of precision on database
358: // side
359: ((Timestamp) fieldvalue).setNanos(0);
360: }
361: ObjectUtils.setObjectProperty(
362: persistableObject, fieldName,
363: fieldvalue.getClass(), fieldvalue);
364: setValue = ObjectUtils.getPropertyValue(
365: persistableObject, fieldName);
366: break;
367: } catch (IllegalArgumentException ignored) {
368: continue;
369: } catch (IllegalAccessException ignored) {
370: continue;
371: } catch (InvocationTargetException ignored) {
372: continue;
373: } catch (InstantiationException ignored) {
374: continue;
375: } catch (NoSuchMethodException ignored) {
376: continue;
377: }
378: }
379: } finally {
380: if (null == setValue) {
381: throw new IntrospectionException(
382: "problem invoking setting/retrieving value for '"
383: + persistableObject.getClass()
384: + "." + fieldName
385: + "' no value was returned");
386: }
387: }
388: return new Object[] { fieldName, setValue };
389: }
390: // do not set value for version number and object_id since they are auto
391: // created
392: else if (StringUtils.equalsIgnoreCase(columnName, "ver_nbr")
393: || StringUtils.equalsIgnoreCase(columnName, "obj_id")) {
394: return new Object[] { fieldName, null };
395: }
396: // negate default value for boolean fields
397: else if (fieldTypeClass != null
398: && fieldTypeClass.equals(boolean.class)) {
399: setValue = Boolean.valueOf(!((Boolean) ObjectUtils
400: .getPropertyValue(persistableObject, fieldName))
401: .booleanValue());
402: ObjectUtils.setObjectProperty(persistableObject, fieldName,
403: setValue.getClass(), setValue);
404: return new Object[] { fieldName, setValue };
405: } else {
406: return null;
407: }
408: }
409:
410: }
|