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.service.impl;
017:
018: import java.beans.PropertyDescriptor;
019: import java.lang.reflect.InvocationTargetException;
020: import java.util.ArrayList;
021: import java.util.Collection;
022: import java.util.Collections;
023: import java.util.HashMap;
024: import java.util.Iterator;
025: import java.util.List;
026: import java.util.Map;
027:
028: import org.apache.commons.beanutils.PropertyUtils;
029: import org.apache.commons.lang.StringUtils;
030: import org.kuali.core.bo.BusinessObjectRelationship;
031: import org.kuali.core.bo.PersistableBusinessObject;
032: import org.kuali.core.bo.user.AuthenticationUserId;
033: import org.kuali.core.bo.user.UniversalUser;
034: import org.kuali.core.dao.BusinessObjectDao;
035: import org.kuali.core.exceptions.ObjectNotABusinessObjectRuntimeException;
036: import org.kuali.core.exceptions.ReferenceAttributeDoesntExistException;
037: import org.kuali.core.exceptions.UserNotFoundException;
038: import org.kuali.core.service.BusinessObjectMetaDataService;
039: import org.kuali.core.service.BusinessObjectService;
040: import org.kuali.core.service.PersistenceService;
041: import org.kuali.core.service.PersistenceStructureService;
042: import org.kuali.core.service.UniversalUserService;
043: import org.kuali.core.util.ObjectUtils;
044: import org.springframework.transaction.annotation.Transactional;
045:
046: /**
047: * This class is the service implementation for the BusinessObjectService structure. This is the default implementation, that is
048: * delivered with Kuali.
049: */
050: @Transactional
051: public class BusinessObjectServiceImpl implements BusinessObjectService {
052:
053: private PersistenceService persistenceService;
054: private PersistenceStructureService persistenceStructureService;
055: private BusinessObjectDao businessObjectDao;
056: private UniversalUserService universalUserService;
057: private BusinessObjectMetaDataService businessObjectMetaDataService;
058:
059: /**
060: * @see org.kuali.core.service.BusinessObjectService#save(org.kuali.bo.BusinessObject)
061: */
062: public void save(PersistableBusinessObject bo) {
063: if (!(bo instanceof PersistableBusinessObject)) {
064: throw new IllegalArgumentException(
065: "Object passed in is not a BusinessObject class or subclass.");
066: }
067: businessObjectDao.save(bo);
068: }
069:
070: /**
071: * @see org.kuali.core.service.BusinessObjectService#save(java.util.List)
072: */
073: public void save(List businessObjects) {
074: int index = 0;
075: for (Iterator i = businessObjects.iterator(); i.hasNext(); index++) {
076: Object current = i.next();
077: if (!(current instanceof PersistableBusinessObject)) {
078: throw new IllegalArgumentException("item '" + index
079: + "' on the given list is not a BusinessObject");
080: }
081: }
082: businessObjectDao.save(businessObjects);
083: }
084:
085: /**
086: *
087: * @see org.kuali.core.service.BusinessObjectService#linkAndSave(org.kuali.core.bo.BusinessObject)
088: */
089: public void linkAndSave(PersistableBusinessObject bo) {
090: if (!(bo instanceof PersistableBusinessObject)) {
091: throw new IllegalArgumentException(
092: "Object passed in is not a BusinessObject class or subclass.");
093: }
094: persistenceService.linkObjects(bo);
095: businessObjectDao.save(bo);
096: }
097:
098: /**
099: *
100: * @see org.kuali.core.service.BusinessObjectService#linkAndSave(java.util.List)
101: */
102: public void linkAndSave(
103: List<PersistableBusinessObject> businessObjects) {
104: for (PersistableBusinessObject bo : businessObjects) {
105: if (!(bo instanceof PersistableBusinessObject)) {
106: throw new IllegalArgumentException(
107: "One of the items in the list passed in is not "
108: + "a BusinessObject descendent: ["
109: + bo.getClass().getName() + "] "
110: + bo.toString());
111: }
112: persistenceService.linkObjects(bo);
113: }
114: businessObjectDao.save(businessObjects);
115: }
116:
117: /**
118: * @see org.kuali.core.service.BusinessObjectService#findByPrimaryKey(java.lang.Class, java.util.Map)
119: */
120: public PersistableBusinessObject findByPrimaryKey(Class clazz,
121: Map primaryKeys) {
122: return businessObjectDao.findByPrimaryKey(clazz, primaryKeys);
123: }
124:
125: /**
126: * @see org.kuali.core.service.BusinessObjectService#retrieve(java.lang.Object)
127: */
128: public PersistableBusinessObject retrieve(
129: PersistableBusinessObject object) {
130: return businessObjectDao.retrieve(object);
131: }
132:
133: /**
134: * @see org.kuali.core.service.BusinessObjectService#findAll(java.lang.Class)
135: */
136: public Collection findAll(Class clazz) {
137: Collection coll = businessObjectDao.findAll(clazz);
138: return new ArrayList(coll);
139: }
140:
141: /**
142: * @see org.kuali.core.service.BusinessObjectService#findMatching(java.lang.Class, java.util.Map)
143: */
144: public Collection findMatching(Class clazz, Map fieldValues) {
145: return new ArrayList(businessObjectDao.findMatching(clazz,
146: fieldValues));
147: }
148:
149: /**
150: * @see org.kuali.core.service.BusinessObjectService#countMatching(java.lang.Class, java.util.Map)
151: */
152: public int countMatching(Class clazz, Map fieldValues) {
153: return businessObjectDao.countMatching(clazz, fieldValues);
154: }
155:
156: /**
157: * @see org.kuali.core.service.BusinessObjectService#countMatching(java.lang.Class, java.util.Map, java.util.Map)
158: */
159: public int countMatching(Class clazz, Map positiveFieldValues,
160: Map negativeFieldValues) {
161: return businessObjectDao.countMatching(clazz,
162: positiveFieldValues, negativeFieldValues);
163: }
164:
165: /**
166: * @see org.kuali.core.service.BusinessObjectService#findMatchingOrderBy(java.lang.Class, java.util.Map)
167: */
168: public Collection findMatchingOrderBy(Class clazz, Map fieldValues,
169: String sortField, boolean sortAscending) {
170: return new ArrayList(businessObjectDao.findMatchingOrderBy(
171: clazz, fieldValues, sortField, sortAscending));
172: }
173:
174: /**
175: * @see org.kuali.core.service.BusinessObjectService#delete(org.kuali.bo.BusinessObject)
176: */
177: public void delete(PersistableBusinessObject bo) {
178: businessObjectDao.delete(bo);
179: }
180:
181: /**
182: * @see org.kuali.core.service.BusinessObjectService#delete(java.util.List)
183: */
184: public void delete(List<PersistableBusinessObject> boList) {
185: businessObjectDao.delete(boList);
186: }
187:
188: /**
189: * @see org.kuali.core.service.BusinessObjectService#deleteMatching(java.lang.Class, java.util.Map)
190: */
191: public void deleteMatching(Class clazz, Map fieldValues) {
192: businessObjectDao.deleteMatching(clazz, fieldValues);
193: }
194:
195: /**
196: * @see org.kuali.core.service.BusinessObjectService#getReferenceIfExists(org.kuali.core.bo.BusinessObject, java.lang.String)
197: */
198: public PersistableBusinessObject getReferenceIfExists(
199: PersistableBusinessObject bo, String referenceName) {
200:
201: PersistableBusinessObject referenceBo = null;
202: boolean allFkeysHaveValues = true;
203:
204: // if either argument is null, then we have nothing to do, complain and abort
205: if (ObjectUtils.isNull(bo)) {
206: throw new IllegalArgumentException(
207: "Passed in BusinessObject was null. No processing can be done.");
208: }
209: if (StringUtils.isEmpty(referenceName)) {
210: throw new IllegalArgumentException(
211: "Passed in referenceName was empty or null. No processing can be done.");
212: }
213:
214: // make sure the attribute exists at all, throw exception if not
215: PropertyDescriptor propertyDescriptor;
216: try {
217: propertyDescriptor = PropertyUtils.getPropertyDescriptor(
218: bo, referenceName);
219: } catch (Exception e) {
220: throw new RuntimeException(e);
221: }
222: if (propertyDescriptor == null) {
223: throw new ReferenceAttributeDoesntExistException(
224: "Requested attribute: '" + referenceName
225: + "' does not exist " + "on class: '"
226: + bo.getClass().getName() + "'. GFK");
227: }
228:
229: // get the class of the attribute name
230: Class referenceClass = ObjectUtils.getPropertyType(bo,
231: referenceName, persistenceStructureService);
232: if (referenceClass == null) {
233: referenceClass = propertyDescriptor.getPropertyType();
234: }
235:
236: /*
237: * check for UniversalUser references in which case we can just get the reference through propertyutils
238: */
239: if (UniversalUser.class.isAssignableFrom(referenceClass)) {
240: try {
241: return (PersistableBusinessObject) PropertyUtils
242: .getProperty(bo, referenceName);
243: } catch (IllegalAccessException e) {
244: throw new RuntimeException(e.getMessage());
245: } catch (InvocationTargetException e) {
246: throw new RuntimeException(e.getMessage());
247: } catch (NoSuchMethodException e) {
248: throw new RuntimeException(e.getMessage());
249: }
250: }
251:
252: // make sure the class of the attribute descends from BusinessObject,
253: // otherwise throw an exception
254: if (!PersistableBusinessObject.class
255: .isAssignableFrom(referenceClass)) {
256: throw new ObjectNotABusinessObjectRuntimeException(
257: "Attribute requested ("
258: + referenceName
259: + ") is of class: "
260: + "'"
261: + referenceClass.getName()
262: + "' and is not a "
263: + "descendent of BusinessObject. Only descendents of BusinessObject "
264: + "can be used.");
265: }
266:
267: // get the list of foreign-keys for this reference. if the reference
268: // does not exist, or is not a reference-descriptor, an exception will
269: // be thrown here.
270: BusinessObjectRelationship boRel = businessObjectMetaDataService
271: .getBusinessObjectRelationship(bo, referenceName);
272: Map<String, String> fkMap = null;
273: if (boRel != null) {
274: fkMap = boRel.getParentToChildReferences();
275: } else {
276: fkMap = Collections.EMPTY_MAP;
277: }
278:
279: // walk through the foreign keys, testing each one to see if it has a value
280: Map pkMap = new HashMap();
281: for (Iterator iter = fkMap.keySet().iterator(); iter.hasNext();) {
282: String fkFieldName = (String) iter.next();
283: String pkFieldName = (String) fkMap.get(fkFieldName);
284:
285: // attempt to retrieve the value for the given field
286: Object fkFieldValue;
287: try {
288: fkFieldValue = PropertyUtils.getProperty(bo,
289: fkFieldName);
290: } catch (Exception e) {
291: throw new RuntimeException(e);
292: }
293:
294: // determine if there is a value for the field
295: if (ObjectUtils.isNull(fkFieldValue)) {
296: allFkeysHaveValues = false;
297: break; // no reason to continue processing the fkeys
298: } else if (String.class.isAssignableFrom(fkFieldValue
299: .getClass())) {
300: if (StringUtils.isEmpty((String) fkFieldValue)) {
301: allFkeysHaveValues = false;
302: break;
303: } else {
304: pkMap.put(pkFieldName, fkFieldValue);
305: }
306: }
307:
308: // if there is a value, grab it
309: else {
310: pkMap.put(pkFieldName, fkFieldValue);
311: }
312: }
313:
314: // only do the retrieval if all Foreign Keys have values
315: if (allFkeysHaveValues) {
316: referenceBo = findByPrimaryKey(referenceClass, pkMap);
317: }
318:
319: // return what we have, it'll be null if it was never retrieved
320: return referenceBo;
321: }
322:
323: /**
324: *
325: * @see org.kuali.core.service.BusinessObjectService#linkUserFields(org.kuali.core.bo.BusinessObject)
326: */
327: public void linkUserFields(PersistableBusinessObject bo) {
328: if (bo == null) {
329: throw new IllegalArgumentException("bo passed in was null");
330: }
331:
332: bo.linkEditableUserFields();
333:
334: List bos = new ArrayList();
335: bos.add(bo);
336: linkUserFields(bos);
337: }
338:
339: /**
340: *
341: * @see org.kuali.core.service.BusinessObjectService#linkUserFields(java.util.List)
342: */
343: public void linkUserFields(List<PersistableBusinessObject> bos) {
344:
345: // do nothing if there's nothing to process
346: if (bos == null) {
347: throw new IllegalArgumentException(
348: "List of bos passed in was null");
349: } else if (bos.isEmpty()) {
350: return;
351: }
352:
353: UniversalUser universalUser = null;
354:
355: for (PersistableBusinessObject bo : bos) {
356: // get a list of the reference objects on the BO
357: List<BusinessObjectRelationship> relationships = businessObjectMetaDataService
358: .getBusinessObjectRelationships(bo);
359: for (BusinessObjectRelationship rel : relationships) {
360: if (UniversalUser.class.equals(rel.getRelatedClass())) {
361: universalUser = (UniversalUser) ObjectUtils
362: .getPropertyValue(bo, rel
363: .getParentAttributeName());
364: if (universalUser != null) {
365: // find the universal user ID relationship and link the field
366: for (Map.Entry<String, String> entry : rel
367: .getParentToChildReferences()
368: .entrySet()) {
369: if (entry.getValue().equals(
370: "personUniversalIdentifier")) {
371: linkUserReference(bo, universalUser,
372: rel.getParentAttributeName(),
373: entry.getKey());
374: break;
375: }
376: }
377: }
378: }
379: }
380:
381: Map<String, Class> references = persistenceStructureService
382: .listReferenceObjectFields(bo);
383:
384: // walk through the ref objects, only doing work if they are KualiUser or UniversalUser
385: for (Iterator<String> iter = references.keySet().iterator(); iter
386: .hasNext();) {
387: String refField = "";
388: Class refClass = null;
389: refField = iter.next();
390: refClass = references.get(refField);
391: if (UniversalUser.class.equals(refClass)) {
392: String fkFieldName = persistenceStructureService
393: .getForeignKeyFieldName(bo.getClass(),
394: refField,
395: "personUniversalIdentifier");
396: universalUser = (UniversalUser) ObjectUtils
397: .getPropertyValue(bo, refField);
398: if (universalUser != null) {
399: linkUserReference(bo, universalUser, refField,
400: fkFieldName);
401: }
402: }
403: }
404: }
405: }
406:
407: /**
408: *
409: * This method links a single UniveralUser back to the parent BO based on the authoritative personUserIdentifier.
410: *
411: * @param bo
412: * @param referenceFieldName
413: * @param referenceClass
414: */
415: private void linkUserReference(PersistableBusinessObject bo,
416: UniversalUser user, String refFieldName, String fkFieldName) {
417:
418: // if the UserId field is blank, there's nothing we can do, so quit
419: if (StringUtils.isBlank(user.getPersonUserIdentifier())) {
420: return;
421: }
422:
423: // attempt to load the user from the user-name, exit quietly if the user isnt found
424: UniversalUser userFromDb = getUniversalUserFromUserName(user
425: .getPersonUserIdentifier().toUpperCase());
426: if (userFromDb == null) {
427: return;
428: }
429:
430: // attempt to set the universalId on the parent BO
431: setBoField(bo, fkFieldName, userFromDb
432: .getPersonUniversalIdentifier());
433:
434: // setup a minimally populated user object ... this is not getting fully populated as this
435: // seems to cause errors in XStream, due to the system not really expecting all the sub-objects
436: // to be fully populated
437: UniversalUser newUserObject = new UniversalUser();
438: newUserObject.setPersonUniversalIdentifier(userFromDb
439: .getPersonUniversalIdentifier());
440: newUserObject.setPersonUserIdentifier(userFromDb
441: .getPersonUserIdentifier());
442:
443: // attempt to set the minimally populated user object to the parent BO
444: setBoField(bo, refFieldName, newUserObject);
445: }
446:
447: private void setBoField(PersistableBusinessObject bo,
448: String fieldName, Object fieldValue) {
449: try {
450: ObjectUtils.setObjectProperty(bo, fieldName, fieldValue
451: .getClass(), fieldValue);
452: } catch (Exception e) {
453: throw new RuntimeException("Could not set field ["
454: + fieldName + "] on BO to value: "
455: + fieldValue.toString()
456: + " (see nested exception for details).", e);
457: }
458: }
459:
460: /**
461: *
462: * This method obtains a populated UniversalUser instance, if one exists by the userName, and returns it. Null instance is
463: * returned if an account cannot be found by userName.
464: *
465: * @param userName String containing the userName.
466: * @return Returns a populated UniversalUser for the given userName, or Null if a record cannot be found for this userName
467: *
468: */
469: private UniversalUser getUniversalUserFromUserName(String userName) {
470: UniversalUser user = null;
471: try {
472: user = universalUserService
473: .getUniversalUser(new AuthenticationUserId(userName));
474: } catch (UserNotFoundException e) {
475: // do nothing, return a null object
476: }
477: return user;
478: }
479:
480: /**
481: * Gets the businessObjectDao attribute.
482: *
483: * @return Returns the businessObjectDao.
484: */
485: public BusinessObjectDao getBusinessObjectDao() {
486: return businessObjectDao;
487: }
488:
489: /**
490: * Sets the businessObjectDao attribute value.
491: *
492: * @param businessObjectDao The businessObjectDao to set.
493: */
494: public void setBusinessObjectDao(BusinessObjectDao businessObjectDao) {
495: this .businessObjectDao = businessObjectDao;
496: }
497:
498: /**
499: * Sets the persistenceStructureService attribute value.
500: *
501: * @param persistenceStructureService The persistenceStructureService to set.
502: */
503: public void setPersistenceStructureService(
504: PersistenceStructureService persistenceStructureService) {
505: this .persistenceStructureService = persistenceStructureService;
506: }
507:
508: /**
509: * Sets the kualiUserService attribute value.
510: *
511: * @param kualiUserService The kualiUserService to set.
512: */
513: public final void setUniversalUserService(
514: UniversalUserService kualiUserService) {
515: this .universalUserService = kualiUserService;
516: }
517:
518: /**
519: * Sets the persistenceService attribute value.
520: *
521: * @param persistenceService The persistenceService to set.
522: */
523: public final void setPersistenceService(
524: PersistenceService persistenceService) {
525: this .persistenceService = persistenceService;
526: }
527:
528: public BusinessObjectMetaDataService getBusinessObjectMetaDataService() {
529: return businessObjectMetaDataService;
530: }
531:
532: public void setBusinessObjectMetaDataService(
533: BusinessObjectMetaDataService boMetadataService) {
534: this.businessObjectMetaDataService = boMetadataService;
535: }
536:
537: }
|