001 /**
002 * Copyright 2005-2012 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.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/ecl2.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.rice.krad.service.impl;
017
018 import org.apache.commons.beanutils.PropertyUtils;
019 import org.apache.commons.lang.StringUtils;
020 import org.kuali.rice.core.api.config.property.ConfigContext;
021 import org.kuali.rice.kim.api.identity.Person;
022 import org.kuali.rice.kim.api.identity.PersonService;
023 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
024 import org.kuali.rice.krad.bo.BusinessObject;
025 import org.kuali.rice.krad.bo.DataObjectRelationship;
026 import org.kuali.rice.krad.bo.ExternalizableBusinessObject;
027 import org.kuali.rice.krad.bo.PersistableBusinessObject;
028 import org.kuali.rice.krad.dao.BusinessObjectDao;
029 import org.kuali.rice.krad.exception.ObjectNotABusinessObjectRuntimeException;
030 import org.kuali.rice.krad.exception.ReferenceAttributeDoesntExistException;
031 import org.kuali.rice.krad.service.BusinessObjectService;
032 import org.kuali.rice.krad.service.DataObjectMetaDataService;
033 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
034 import org.kuali.rice.krad.service.ModuleService;
035 import org.kuali.rice.krad.service.PersistenceService;
036 import org.kuali.rice.krad.service.PersistenceStructureService;
037 import org.kuali.rice.krad.util.KRADConstants;
038 import org.kuali.rice.krad.util.ObjectUtils;
039 import org.springframework.transaction.annotation.Transactional;
040
041 import java.beans.PropertyDescriptor;
042 import java.util.Collection;
043 import java.util.Collections;
044 import java.util.HashMap;
045 import java.util.HashSet;
046 import java.util.List;
047 import java.util.Map;
048 import java.util.Set;
049
050 /**
051 * This class is the service implementation for the BusinessObjectService structure. This is the default implementation, that is
052 * delivered with Kuali.
053 */
054
055 public class BusinessObjectServiceImpl implements BusinessObjectService {
056
057 private PersistenceService persistenceService;
058 private PersistenceStructureService persistenceStructureService;
059 private BusinessObjectDao businessObjectDao;
060 private PersonService personService;
061 private DataObjectMetaDataService dataObjectMetaDataService;
062
063 private boolean illegalBusinessObjectsForSaveInitialized;
064 private final Set<String> illegalBusinessObjectsForSave = new HashSet<String>();
065
066 @Override
067 @Transactional
068 public <T extends PersistableBusinessObject> T save(T bo) {
069 validateBusinessObjectForSave(bo);
070 return (T) businessObjectDao.save(bo);
071 }
072
073 @Override
074 @Transactional
075 public List<? extends PersistableBusinessObject> save(List<? extends PersistableBusinessObject> businessObjects) {
076 validateBusinessObjectForSave(businessObjects);
077 return businessObjectDao.save(businessObjects);
078 }
079
080 @Override
081 @Transactional
082 public PersistableBusinessObject linkAndSave(PersistableBusinessObject bo) {
083 validateBusinessObjectForSave(bo);
084 persistenceService.linkObjects(bo);
085 return businessObjectDao.save(bo);
086 }
087
088 @Override
089 @Transactional
090 public List<? extends PersistableBusinessObject> linkAndSave(List<? extends PersistableBusinessObject> businessObjects) {
091 validateBusinessObjectForSave(businessObjects);
092 return businessObjectDao.save(businessObjects);
093 }
094
095 protected void validateBusinessObjectForSave(PersistableBusinessObject bo) {
096 if (bo == null) {
097 throw new IllegalArgumentException("Object passed in is null");
098 }
099 if (!isBusinessObjectAllowedForSave(bo)) {
100 throw new IllegalArgumentException("Object passed in is a BusinessObject but has been restricted from save operations according to configuration parameter '" + KRADConstants.Config.ILLEGAL_BUSINESS_OBJECTS_FOR_SAVE);
101 }
102 }
103
104 protected void validateBusinessObjectForSave(List<? extends PersistableBusinessObject> businessObjects) {
105 for (PersistableBusinessObject bo : businessObjects) {
106 if (bo == null) {
107 throw new IllegalArgumentException("One of the objects in the List is null.");
108 }
109 if (!isBusinessObjectAllowedForSave(bo)) {
110 throw new IllegalArgumentException("One of the objects in the List is a BusinessObject but has been restricted from save operations according to configuration parameter '" + KRADConstants.Config.ILLEGAL_BUSINESS_OBJECTS_FOR_SAVE
111 + " Passed in type was '" + bo.getClass().getName() + "'.");
112 }
113 }
114 }
115
116
117 /**
118 * Returns true if the BusinessObjectService should be permitted to save instances of the given PersistableBusinessObject.
119 * Implementation checks a configuration parameter for class names of PersistableBusinessObjects that shouldn't be allowed
120 * to be saved.
121 */
122 protected boolean isBusinessObjectAllowedForSave(PersistableBusinessObject bo) {
123 if (!illegalBusinessObjectsForSaveInitialized) {
124 synchronized (this) {
125 boolean applyCheck = true;
126 String applyCheckValue = ConfigContext.getCurrentContextConfig().getProperty(KRADConstants.Config.APPLY_ILLEGAL_BUSINESS_OBJECT_FOR_SAVE_CHECK);
127 if (!StringUtils.isEmpty(applyCheckValue)) {
128 applyCheck = Boolean.valueOf(applyCheckValue);
129 }
130 if (applyCheck) {
131 String illegalBos = ConfigContext.getCurrentContextConfig().getProperty(KRADConstants.Config.ILLEGAL_BUSINESS_OBJECTS_FOR_SAVE);
132 if (!StringUtils.isEmpty(illegalBos)) {
133 String[] illegalBosSplit = illegalBos.split(",");
134 for (String illegalBo : illegalBosSplit) {
135 illegalBusinessObjectsForSave.add(illegalBo.trim());
136 }
137 }
138 }
139 }
140 illegalBusinessObjectsForSaveInitialized = true;
141 }
142 return !illegalBusinessObjectsForSave.contains(bo.getClass().getName());
143 }
144
145
146 @Override
147 public <T extends BusinessObject> T findBySinglePrimaryKey(Class<T> clazz, Object primaryKey) {
148 return businessObjectDao.findBySinglePrimaryKey(clazz, primaryKey);
149 }
150 @Override
151 public <T extends BusinessObject> T findByPrimaryKey(Class<T> clazz, Map<String, ?> primaryKeys) {
152 return businessObjectDao.findByPrimaryKey(clazz, primaryKeys);
153 }
154
155 @Override
156 public PersistableBusinessObject retrieve(PersistableBusinessObject object) {
157 return businessObjectDao.retrieve(object);
158 }
159
160 @Override
161 public <T extends BusinessObject> Collection<T> findAll(Class<T> clazz) {
162 return businessObjectDao.findAll(clazz);
163 }
164 @Override
165 public <T extends BusinessObject> Collection<T> findAllOrderBy( Class<T> clazz, String sortField, boolean sortAscending ) {
166 final Map<String, ?> emptyParameters = Collections.emptyMap();
167 return businessObjectDao.findMatchingOrderBy(clazz, emptyParameters, sortField, sortAscending );
168 }
169
170 @Override
171 public <T extends BusinessObject> Collection<T> findMatching(Class<T> clazz, Map<String, ?> fieldValues) {
172 return businessObjectDao.findMatching(clazz, fieldValues);
173 }
174
175 @Override
176 public int countMatching(Class clazz, Map<String, ?> fieldValues) {
177 return businessObjectDao.countMatching(clazz, fieldValues);
178 }
179
180 @Override
181 public int countMatching(Class clazz, Map<String, ?> positiveFieldValues, Map<String, ?> negativeFieldValues) {
182 return businessObjectDao.countMatching(clazz, positiveFieldValues, negativeFieldValues);
183 }
184 @Override
185 public <T extends BusinessObject> Collection<T> findMatchingOrderBy(Class<T> clazz, Map<String, ?> fieldValues, String sortField, boolean sortAscending) {
186 return businessObjectDao.findMatchingOrderBy(clazz, fieldValues, sortField, sortAscending);
187 }
188 @Override
189 @Transactional
190 public void delete(PersistableBusinessObject bo) {
191 businessObjectDao.delete(bo);
192 }
193
194 @Override
195 @Transactional
196 public void delete(List<? extends PersistableBusinessObject> boList) {
197 businessObjectDao.delete(boList);
198 }
199
200 @Override
201 @Transactional
202 public void deleteMatching(Class clazz, Map<String, ?> fieldValues) {
203 businessObjectDao.deleteMatching(clazz, fieldValues);
204 }
205
206 @Override
207 public BusinessObject getReferenceIfExists(BusinessObject bo, String referenceName) {
208 // if either argument is null, then we have nothing to do, complain and abort
209 if (ObjectUtils.isNull(bo)) {
210 throw new IllegalArgumentException("Passed in BusinessObject was null. No processing can be done.");
211 }
212 if (StringUtils.isEmpty(referenceName)) {
213 throw new IllegalArgumentException("Passed in referenceName was empty or null. No processing can be done.");
214 }
215
216 // make sure the attribute exists at all, throw exception if not
217 PropertyDescriptor propertyDescriptor;
218 try {
219 propertyDescriptor = PropertyUtils.getPropertyDescriptor(bo, referenceName);
220 }
221 catch (Exception e) {
222 throw new RuntimeException(e);
223 }
224 if (propertyDescriptor == null) {
225 throw new ReferenceAttributeDoesntExistException("Requested attribute: '" + referenceName + "' does not exist " + "on class: '" + bo.getClass().getName() + "'. GFK");
226 }
227
228 // get the class of the attribute name
229 Class referenceClass = ObjectUtils.getPropertyType( bo, referenceName, persistenceStructureService );
230 if ( referenceClass == null ) {
231 referenceClass = propertyDescriptor.getPropertyType();
232 }
233
234 /*
235 * check for Person or EBO references in which case we can just get the reference through propertyutils
236 */
237 if (ExternalizableBusinessObject.class.isAssignableFrom(referenceClass)) {
238 try {
239 BusinessObject referenceBoExternalizable = (BusinessObject) PropertyUtils.getProperty(bo, referenceName);
240 if (referenceBoExternalizable!=null) {
241 return referenceBoExternalizable;
242 }
243 } catch (Exception ex) {
244 //throw new RuntimeException("Unable to get property " + referenceName + " from a BO of class: " + bo.getClass().getName(),ex);
245 //Proceed further - get the BO relationship using responsible module service and proceed further
246 }
247 }
248
249 // make sure the class of the attribute descends from BusinessObject,
250 // otherwise throw an exception
251 if (!ExternalizableBusinessObject.class.isAssignableFrom(referenceClass) && !PersistableBusinessObject.class.isAssignableFrom(referenceClass)) {
252 throw new ObjectNotABusinessObjectRuntimeException("Attribute requested (" + referenceName + ") is of class: " + "'" + referenceClass.getName() + "' and is not a " + "descendent of PersistableBusinessObject. Only descendents of PersistableBusinessObject " + "can be used.");
253 }
254
255 // get the list of foreign-keys for this reference. if the reference
256 // does not exist, or is not a reference-descriptor, an exception will
257 // be thrown here.
258 //DataObjectRelationship boRel = dataObjectMetaDataService.getBusinessObjectRelationship( bo, referenceName );
259 DataObjectRelationship boRel = dataObjectMetaDataService.getDataObjectRelationship(bo, bo.getClass(),
260 referenceName, "", true, false, false);
261 final Map<String,String> fkMap = boRel != null ? boRel.getParentToChildReferences() : Collections.<String, String>emptyMap();
262
263 boolean allFkeysHaveValues = true;
264 // walk through the foreign keys, testing each one to see if it has a value
265 Map<String,Object> pkMap = new HashMap<String,Object>();
266 for (Map.Entry<String, String> entry : fkMap.entrySet()) {
267 String fkFieldName = entry.getKey();
268 String pkFieldName = entry.getValue();
269
270 // attempt to retrieve the value for the given field
271 Object fkFieldValue;
272 try {
273 fkFieldValue = PropertyUtils.getProperty(bo, fkFieldName);
274 }
275 catch (Exception e) {
276 throw new RuntimeException(e);
277 }
278
279 // determine if there is a value for the field
280 if (ObjectUtils.isNull(fkFieldValue)) {
281 allFkeysHaveValues = false;
282 break; // no reason to continue processing the fkeys
283 }
284 else if (String.class.isAssignableFrom(fkFieldValue.getClass())) {
285 if (StringUtils.isEmpty((String) fkFieldValue)) {
286 allFkeysHaveValues = false;
287 break;
288 }
289 else {
290 pkMap.put(pkFieldName, fkFieldValue);
291 }
292 }
293
294 // if there is a value, grab it
295 else {
296 pkMap.put(pkFieldName, fkFieldValue);
297 }
298 }
299
300 BusinessObject referenceBo = null;
301 // only do the retrieval if all Foreign Keys have values
302 if (allFkeysHaveValues) {
303 if (ExternalizableBusinessObject.class.isAssignableFrom(referenceClass)) {
304 ModuleService responsibleModuleService = KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(referenceClass);
305 if(responsibleModuleService!=null) {
306 return responsibleModuleService.<ExternalizableBusinessObject>getExternalizableBusinessObject(referenceClass, pkMap);
307 }
308 } else
309 referenceBo = this.<BusinessObject>findByPrimaryKey(referenceClass, pkMap);
310 }
311
312 // return what we have, it'll be null if it was never retrieved
313 return referenceBo;
314 }
315 @Override
316 public void linkUserFields(PersistableBusinessObject bo) {
317 if (bo == null) {
318 throw new IllegalArgumentException("bo passed in was null");
319 }
320
321 bo.linkEditableUserFields();
322
323 linkUserFields( Collections.singletonList( bo ) );
324 }
325
326 @Override
327 public void linkUserFields(List<PersistableBusinessObject> bos) {
328
329 // do nothing if there's nothing to process
330 if (bos == null) {
331 throw new IllegalArgumentException("List of bos passed in was null");
332 }
333 else if (bos.isEmpty()) {
334 return;
335 }
336
337
338 Person person;
339 for (PersistableBusinessObject bo : bos) {
340 // get a list of the reference objects on the BO
341 List<DataObjectRelationship> relationships = dataObjectMetaDataService.getDataObjectRelationships(
342 bo.getClass());
343 for ( DataObjectRelationship rel : relationships ) {
344 if ( Person.class.isAssignableFrom( rel.getRelatedClass() ) ) {
345 person = (Person) ObjectUtils.getPropertyValue(bo, rel.getParentAttributeName() );
346 if (person != null) {
347 // find the universal user ID relationship and link the field
348 for ( Map.Entry<String,String> entry : rel.getParentToChildReferences().entrySet() ) {
349 if ( "principalId".equals(entry.getValue())) {
350 linkUserReference(bo, person, rel.getParentAttributeName(), entry.getKey() );
351 break;
352 }
353 }
354 }
355 }
356 }
357 if ( persistenceStructureService.isPersistable(bo.getClass())) {
358 Map<String, Class> references = persistenceStructureService.listReferenceObjectFields(bo);
359
360 // walk through the ref objects, only doing work if they are Person objects
361 for ( Map.Entry<String, Class> entry : references.entrySet() ) {
362 if (Person.class.isAssignableFrom(entry.getValue())) {
363 person = (Person) ObjectUtils.getPropertyValue(bo, entry.getKey());
364 if (person != null) {
365 String fkFieldName = persistenceStructureService.getForeignKeyFieldName(bo.getClass(), entry.getKey(), "principalId");
366 linkUserReference(bo, person, entry.getKey(), fkFieldName);
367 }
368 }
369 }
370 }
371 }
372 }
373
374 /**
375 *
376 * This method links a single UniveralUser back to the parent BO based on the authoritative principalName.
377 *
378 * @param bo
379 * @param refFieldName
380 */
381 private void linkUserReference(PersistableBusinessObject bo, Person user, String refFieldName, String fkFieldName) {
382
383 // if the UserId field is blank, there's nothing we can do, so quit
384 if (StringUtils.isBlank(user.getPrincipalName())) {
385 return;
386 }
387
388 // attempt to load the user from the user-name, exit quietly if the user isnt found
389 Person userFromService = getPersonService().getPersonByPrincipalName(user.getPrincipalName());
390 if (userFromService == null) {
391 return;
392 }
393
394 // attempt to set the universalId on the parent BO
395 setBoField(bo, fkFieldName, userFromService.getPrincipalId());
396 }
397
398 private void setBoField(PersistableBusinessObject bo, String fieldName, Object fieldValue) {
399 try {
400 ObjectUtils.setObjectProperty(bo, fieldName, fieldValue.getClass(), fieldValue);
401 }
402 catch (Exception e) {
403 throw new RuntimeException("Could not set field [" + fieldName + "] on BO to value: " + fieldValue.toString() + " (see nested exception for details).", e);
404 }
405 }
406
407 @Override
408 public PersistableBusinessObject manageReadOnly(PersistableBusinessObject bo) {
409 return getBusinessObjectDao().manageReadOnly(bo);
410 }
411
412 /**
413 * Gets the businessObjectDao attribute.
414 *
415 * @return Returns the businessObjectDao.
416 */
417 protected BusinessObjectDao getBusinessObjectDao() {
418 return businessObjectDao;
419 }
420
421 /**
422 * Sets the businessObjectDao attribute value.
423 *
424 * @param businessObjectDao The businessObjectDao to set.
425 */
426 public void setBusinessObjectDao(BusinessObjectDao businessObjectDao) {
427 this.businessObjectDao = businessObjectDao;
428 }
429
430 /**
431 * Sets the persistenceStructureService attribute value.
432 *
433 * @param persistenceStructureService The persistenceStructureService to set.
434 */
435 public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
436 this.persistenceStructureService = persistenceStructureService;
437 }
438
439 /**
440 * Sets the kualiUserService attribute value.
441 */
442 public final void setPersonService(PersonService personService) {
443 this.personService = personService;
444 }
445
446 protected PersonService getPersonService() {
447 return personService != null ? personService : (personService = KimApiServiceLocator.getPersonService());
448 }
449
450 /**
451 * Sets the persistenceService attribute value.
452 *
453 * @param persistenceService The persistenceService to set.
454 */
455 public final void setPersistenceService(PersistenceService persistenceService) {
456 this.persistenceService = persistenceService;
457 }
458
459 protected DataObjectMetaDataService getDataObjectMetaDataService() {
460 return dataObjectMetaDataService;
461 }
462
463 public void setDataObjectMetaDataService(DataObjectMetaDataService dataObjectMetadataService) {
464 this.dataObjectMetaDataService = dataObjectMetadataService;
465 }
466
467 }