001 /**
002 * Copyright 2005-2013 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.maintenance;
017
018 import org.apache.commons.lang.StringUtils;
019 import org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException;
020 import org.kuali.rice.core.api.CoreApiServiceLocator;
021 import org.kuali.rice.core.api.encryption.EncryptionService;
022 import org.kuali.rice.kim.api.identity.Person;
023 import org.kuali.rice.krad.bo.AdHocRoutePerson;
024 import org.kuali.rice.krad.bo.AdHocRouteWorkgroup;
025 import org.kuali.rice.krad.bo.BusinessObject;
026 import org.kuali.rice.krad.bo.DocumentHeader;
027 import org.kuali.rice.krad.bo.Note;
028 import org.kuali.rice.krad.bo.PersistableBusinessObject;
029 import org.kuali.rice.krad.exception.PessimisticLockingException;
030 import org.kuali.rice.krad.service.BusinessObjectService;
031 import org.kuali.rice.krad.service.DataObjectAuthorizationService;
032 import org.kuali.rice.krad.service.DataObjectMetaDataService;
033 import org.kuali.rice.krad.service.DocumentDictionaryService;
034 import org.kuali.rice.krad.service.KRADServiceLocator;
035 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
036 import org.kuali.rice.krad.service.LookupService;
037 import org.kuali.rice.krad.service.MaintenanceDocumentService;
038 import org.kuali.rice.krad.uif.container.CollectionGroup;
039 import org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl;
040 import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
041 import org.kuali.rice.krad.uif.view.View;
042 import org.kuali.rice.krad.util.KRADConstants;
043 import org.kuali.rice.krad.util.ObjectUtils;
044 import org.kuali.rice.krad.web.form.MaintenanceDocumentForm;
045
046 import java.security.GeneralSecurityException;
047 import java.util.ArrayList;
048 import java.util.Collection;
049 import java.util.Iterator;
050 import java.util.List;
051 import java.util.Map;
052
053 /**
054 * Default implementation of the <code>Maintainable</code> interface
055 *
056 * @author Kuali Rice Team (rice.collab@kuali.org)
057 */
058 public class MaintainableImpl extends ViewHelperServiceImpl implements Maintainable {
059 private static final long serialVersionUID = 9125271369161634992L;
060
061 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(MaintainableImpl.class);
062
063 private String documentNumber;
064 private Object dataObject;
065 private Class<?> dataObjectClass;
066 private String maintenanceAction;
067
068 private transient LookupService lookupService;
069 private transient DataObjectAuthorizationService dataObjectAuthorizationService;
070 private transient DataObjectMetaDataService dataObjectMetaDataService;
071 private transient DocumentDictionaryService documentDictionaryService;
072 private transient EncryptionService encryptionService;
073 private transient BusinessObjectService businessObjectService;
074 private transient MaintenanceDocumentService maintenanceDocumentService;
075
076 /**
077 * @see org.kuali.rice.krad.maintenance.Maintainable#retrieveObjectForEditOrCopy(MaintenanceDocument, java.util.Map)
078 */
079 @Override
080 public Object retrieveObjectForEditOrCopy(MaintenanceDocument document, Map<String, String> dataObjectKeys) {
081 Object dataObject = null;
082
083 try {
084 dataObject = getLookupService().findObjectBySearch(getDataObjectClass(), dataObjectKeys);
085 } catch (ClassNotPersistenceCapableException ex) {
086 if (!document.getOldMaintainableObject().isExternalBusinessObject()) {
087 throw new RuntimeException("Data Object Class: "
088 + getDataObjectClass()
089 + " is not persistable and is not externalizable - configuration error");
090 }
091 // otherwise, let fall through
092 }
093
094 return dataObject;
095 }
096
097 /**
098 * @see org.kuali.rice.krad.maintenance.Maintainable#setDocumentNumber
099 */
100 @Override
101 public void setDocumentNumber(String documentNumber) {
102 this.documentNumber = documentNumber;
103 }
104
105 /**
106 * @see org.kuali.rice.krad.maintenance.Maintainable#getDocumentTitle
107 */
108 @Override
109 public String getDocumentTitle(MaintenanceDocument document) {
110 // default implementation is to allow MaintenanceDocumentBase to
111 // generate the doc title
112 return "";
113 }
114
115 /**
116 * @see org.kuali.rice.krad.maintenance.Maintainable#getDataObject
117 */
118 @Override
119 public Object getDataObject() {
120 return dataObject;
121 }
122
123 /**
124 * @see org.kuali.rice.krad.maintenance.Maintainable#setDataObject
125 */
126 @Override
127 public void setDataObject(Object object) {
128 this.dataObject = object;
129 }
130
131 /**
132 * @see org.kuali.rice.krad.maintenance.Maintainable#getDataObjectClass
133 */
134 @Override
135 public Class getDataObjectClass() {
136 return dataObjectClass;
137 }
138
139 /**
140 * @see org.kuali.rice.krad.maintenance.Maintainable#setDataObjectClass
141 */
142 @Override
143 public void setDataObjectClass(Class dataObjectClass) {
144 this.dataObjectClass = dataObjectClass;
145 }
146
147 /**
148 * Persistable business objects are lockable
149 *
150 * @see org.kuali.rice.krad.maintenance.Maintainable#isLockable
151 */
152 @Override
153 public boolean isLockable() {
154 return KRADServiceLocator.getPersistenceStructureService().isPersistable(getDataObject().getClass());
155 }
156
157 /**
158 * Returns the data object if its persistable, null otherwise
159 *
160 * @see org.kuali.rice.krad.maintenance.Maintainable#getPersistableBusinessObject
161 */
162 @Override
163 public PersistableBusinessObject getPersistableBusinessObject() {
164 if (KRADServiceLocator.getPersistenceStructureService().isPersistable(getDataObject().getClass())) {
165 return (PersistableBusinessObject) getDataObject();
166 } else {
167 return null;
168 }
169
170 }
171
172 /**
173 * @see org.kuali.rice.krad.maintenance.Maintainable#getMaintenanceAction
174 */
175 @Override
176 public String getMaintenanceAction() {
177 return maintenanceAction;
178 }
179
180 /**
181 * @see org.kuali.rice.krad.maintenance.Maintainable#setMaintenanceAction
182 */
183 @Override
184 public void setMaintenanceAction(String maintenanceAction) {
185 this.maintenanceAction = maintenanceAction;
186 }
187
188 /**
189 * Note: as currently implemented, every key field for a given
190 * data object class must have a visible getter
191 *
192 * @see org.kuali.rice.krad.maintenance.Maintainable#generateMaintenanceLocks
193 */
194 @Override
195 public List<MaintenanceLock> generateMaintenanceLocks() {
196 List<MaintenanceLock> maintenanceLocks = new ArrayList<MaintenanceLock>();
197 StringBuffer lockRepresentation = new StringBuffer(dataObjectClass.getName());
198 lockRepresentation.append(KRADConstants.Maintenance.LOCK_AFTER_CLASS_DELIM);
199
200 Object bo = getDataObject();
201 List keyFieldNames = getDocumentDictionaryService().getLockingKeys(getDocumentTypeName());
202
203 for (Iterator i = keyFieldNames.iterator(); i.hasNext(); ) {
204 String fieldName = (String) i.next();
205 Object fieldValue = ObjectUtils.getPropertyValue(bo, fieldName);
206 if (fieldValue == null) {
207 fieldValue = "";
208 }
209
210 // check if field is a secure
211 if (getDataObjectAuthorizationService()
212 .attributeValueNeedsToBeEncryptedOnFormsAndLinks(dataObjectClass, fieldName)) {
213 try {
214 if(CoreApiServiceLocator.getEncryptionService().isEnabled()) {
215 fieldValue = getEncryptionService().encrypt(fieldValue);
216 }
217 } catch (GeneralSecurityException e) {
218 LOG.error("Unable to encrypt secure field for locking representation " + e.getMessage());
219 throw new RuntimeException(
220 "Unable to encrypt secure field for locking representation " + e.getMessage());
221 }
222 }
223
224 lockRepresentation.append(fieldName);
225 lockRepresentation.append(KRADConstants.Maintenance.LOCK_AFTER_FIELDNAME_DELIM);
226 lockRepresentation.append(String.valueOf(fieldValue));
227 if (i.hasNext()) {
228 lockRepresentation.append(KRADConstants.Maintenance.LOCK_AFTER_VALUE_DELIM);
229 }
230 }
231
232 MaintenanceLock maintenanceLock = new MaintenanceLock();
233 maintenanceLock.setDocumentNumber(documentNumber);
234 maintenanceLock.setLockingRepresentation(lockRepresentation.toString());
235 maintenanceLocks.add(maintenanceLock);
236
237 return maintenanceLocks;
238 }
239
240 /**
241 * Retrieves the document type name from the data dictionary based on
242 * business object class
243 */
244 protected String getDocumentTypeName() {
245 return getDocumentDictionaryService().getMaintenanceDocumentTypeName(dataObjectClass);
246 }
247
248 /**
249 * @see org.kuali.rice.krad.maintenance.Maintainable#saveDataObject
250 */
251 @Override
252 public void saveDataObject() {
253 if (dataObject instanceof PersistableBusinessObject) {
254 getBusinessObjectService().linkAndSave((PersistableBusinessObject) dataObject);
255 } else {
256 throw new RuntimeException(
257 "Cannot save object of type: " + dataObjectClass + " with business object service");
258 }
259 }
260
261 /**
262 * @see org.kuali.rice.krad.maintenance.Maintainable#deleteDataObject
263 */
264 @Override
265 public void deleteDataObject() {
266 if (dataObject == null) {
267 return;
268 }
269
270 if (dataObject instanceof PersistableBusinessObject) {
271 getBusinessObjectService().delete((PersistableBusinessObject) dataObject);
272 dataObject = null;
273 } else {
274 throw new RuntimeException(
275 "Cannot delete object of type: " + dataObjectClass + " with business object service");
276 }
277 }
278
279 /**
280 * @see org.kuali.rice.krad.maintenance.Maintainable#doRouteStatusChange
281 */
282 @Override
283 public void doRouteStatusChange(DocumentHeader documentHeader) {
284 // no default implementation
285 }
286
287 /**
288 * @see org.kuali.rice.krad.maintenance.Maintainable#getLockingDocumentId
289 */
290 @Override
291 public String getLockingDocumentId() {
292 return getMaintenanceDocumentService().getLockingDocumentId(this, documentNumber);
293 }
294
295 /**
296 * @see org.kuali.rice.krad.maintenance.Maintainable#getWorkflowEngineDocumentIdsToLock
297 */
298 @Override
299 public List<String> getWorkflowEngineDocumentIdsToLock() {
300 return null;
301 }
302
303 /**
304 * Default implementation simply returns false to indicate that custom
305 * lock descriptors are not supported by MaintainableImpl. If custom
306 * lock descriptors are needed, the appropriate subclasses should override
307 * this method
308 *
309 * @see org.kuali.rice.krad.maintenance.Maintainable#useCustomLockDescriptors
310 */
311 @Override
312 public boolean useCustomLockDescriptors() {
313 return false;
314 }
315
316 /**
317 * Default implementation just throws a PessimisticLockingException.
318 * Subclasses of MaintainableImpl that need support for custom lock
319 * descriptors should override this method
320 *
321 * @see org.kuali.rice.krad.maintenance.Maintainable#getCustomLockDescriptor
322 */
323 @Override
324 public String getCustomLockDescriptor(Person user) {
325 throw new PessimisticLockingException("The Maintainable for document " + documentNumber +
326 " is using pessimistic locking with custom lock descriptors, but the Maintainable has not overridden the getCustomLockDescriptor method");
327 }
328
329 /**
330 * @see org.kuali.rice.krad.maintenance.Maintainable#isNotesEnabled
331 */
332 @Override
333 public boolean isNotesEnabled() {
334 return getDataObjectMetaDataService().areNotesSupported(dataObjectClass);
335 }
336
337 /**
338 * @see org.kuali.rice.krad.maintenance.MaintainableImpl#isExternalBusinessObject
339 */
340 @Override
341 public boolean isExternalBusinessObject() {
342 return false;
343 }
344
345 /**
346 * @see org.kuali.rice.krad.maintenance.MaintainableImpl#prepareExternalBusinessObject
347 */
348 @Override
349 public void prepareExternalBusinessObject(BusinessObject businessObject) {
350 // by default do nothing
351 }
352
353 /**
354 * Checks whether the data object is not null and has its primary key values populated
355 *
356 * @see org.kuali.rice.krad.maintenance.MaintainableImpl#isOldDataObjectInDocument
357 */
358 @Override
359 public boolean isOldDataObjectInDocument() {
360 boolean isOldDataObjectInExistence = true;
361
362 if (getDataObject() == null) {
363 isOldDataObjectInExistence = false;
364 } else {
365 Map<String, ?> keyFieldValues = getDataObjectMetaDataService().getPrimaryKeyFieldValues(getDataObject());
366 for (Object keyValue : keyFieldValues.values()) {
367 if (keyValue == null) {
368 isOldDataObjectInExistence = false;
369 } else if ((keyValue instanceof String) && StringUtils.isBlank((String) keyValue)) {
370 isOldDataObjectInExistence = false;
371 }
372
373 if (!isOldDataObjectInExistence) {
374 break;
375 }
376 }
377 }
378
379 return isOldDataObjectInExistence;
380 }
381
382 /**
383 * @see org.kuali.rice.krad.maintenance.Maintainable#prepareForSave
384 */
385 @Override
386 public void prepareForSave() {
387 // by default do nothing
388 }
389
390 /**
391 * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterRetrieve
392 */
393 @Override
394 public void processAfterRetrieve() {
395 // by default do nothing
396 }
397
398 /**
399 * @see org.kuali.rice.krad.maintenance.MaintainableImpl#setupNewFromExisting
400 */
401 @Override
402 public void setupNewFromExisting(MaintenanceDocument document, Map<String, String[]> parameters) {
403 // by default do nothing
404 }
405
406 /**
407 * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterCopy
408 */
409 @Override
410 public void processAfterCopy(MaintenanceDocument document, Map<String, String[]> requestParameters) {
411 // by default do nothing
412 }
413
414 /**
415 * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterEdit
416 */
417 @Override
418 public void processAfterEdit(MaintenanceDocument document, Map<String, String[]> requestParameters) {
419 // by default do nothing
420 }
421
422 /**
423 * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterNew
424 */
425 @Override
426 public void processAfterNew(MaintenanceDocument document, Map<String, String[]> requestParameters) {
427 // by default do nothing
428 }
429
430 /**
431 * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterPost
432 */
433 @Override
434 public void processAfterPost(MaintenanceDocument document, Map<String, String[]> requestParameters) {
435 // by default do nothing
436 }
437
438 /**
439 * In the case of edit maintenance adds a new blank line to the old side
440 *
441 * TODO: should this write some sort of missing message on the old side
442 * instead?
443 *
444 * @see org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl#processAfterAddLine(org.kuali.rice.krad.uif.view.View,
445 * org.kuali.rice.krad.uif.container.CollectionGroup, java.lang.Object,
446 * java.lang.Object)
447 */
448 @Override
449 protected void processAfterAddLine(View view, CollectionGroup collectionGroup, Object model, Object addLine) {
450 super.processAfterAddLine(view, collectionGroup, model, addLine);
451
452 // Check for maintenance documents in edit but exclude notes and ad hoc recipients
453 if (model instanceof MaintenanceDocumentForm
454 && KRADConstants.MAINTENANCE_EDIT_ACTION.equals(((MaintenanceDocumentForm)model).getMaintenanceAction()) && !(addLine instanceof Note) && !(addLine instanceof AdHocRoutePerson) && !(addLine instanceof AdHocRouteWorkgroup)) {
455 MaintenanceDocumentForm maintenanceForm = (MaintenanceDocumentForm) model;
456 MaintenanceDocument document = maintenanceForm.getDocument();
457
458 // get the old object's collection
459 //KULRICE-7970 support multiple level objects
460 String bindingPrefix = collectionGroup.getBindingInfo().getBindByNamePrefix();
461 String propertyPath = collectionGroup.getPropertyName();
462 if(bindingPrefix!=""&&bindingPrefix!= null) {
463 propertyPath = bindingPrefix + "." + propertyPath;
464 }
465
466 Collection<Object> oldCollection = ObjectPropertyUtils
467 .getPropertyValue(document.getOldMaintainableObject().getDataObject(),
468 propertyPath);
469
470
471 try {
472 Object blankLine = collectionGroup.getCollectionObjectClass().newInstance();
473 //Add a blank line to the top of the collection
474 if(oldCollection instanceof List){
475 ((List) oldCollection).add(0,blankLine);
476 } else {
477 oldCollection.add(blankLine);
478 }
479 } catch (Exception e) {
480 throw new RuntimeException("Unable to create new line instance for old maintenance object", e);
481 }
482 }
483 }
484
485 /**
486 * In the case of edit maintenance deleted the item on the old side
487 *
488 *
489 * @see org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl#processAfterDeleteLine(View,
490 * org.kuali.rice.krad.uif.container.CollectionGroup, java.lang.Object, int)
491 */
492 @Override
493 protected void processAfterDeleteLine(View view, CollectionGroup collectionGroup, Object model, int lineIndex) {
494 super.processAfterDeleteLine(view, collectionGroup, model, lineIndex);
495
496 // Check for maintenance documents in edit but exclude notes and ad hoc recipients
497 if (model instanceof MaintenanceDocumentForm
498 && KRADConstants.MAINTENANCE_EDIT_ACTION.equals(((MaintenanceDocumentForm)model).getMaintenanceAction())
499 && !collectionGroup.getCollectionObjectClass().getName().equals(Note.class.getName())
500 && !collectionGroup.getCollectionObjectClass().getName().equals(AdHocRoutePerson.class.getName())
501 && !collectionGroup.getCollectionObjectClass().getName().equals(AdHocRouteWorkgroup.class.getName())) {
502 MaintenanceDocumentForm maintenanceForm = (MaintenanceDocumentForm) model;
503 MaintenanceDocument document = maintenanceForm.getDocument();
504
505 // get the old object's collection
506 Collection<Object> oldCollection = ObjectPropertyUtils
507 .getPropertyValue(document.getOldMaintainableObject().getDataObject(),
508 collectionGroup.getPropertyName());
509 try {
510 // Remove the object at lineIndex from the collection
511 oldCollection.remove(oldCollection.toArray()[lineIndex]);
512 } catch (Exception e) {
513 throw new RuntimeException("Unable to delete line instance for old maintenance object", e);
514 }
515 }
516 }
517
518 /**
519 * Retrieves the document number configured on this maintainable
520 *
521 * @return String document number
522 */
523 protected String getDocumentNumber() {
524 return this.documentNumber;
525 }
526
527 protected LookupService getLookupService() {
528 if (lookupService == null) {
529 lookupService = KRADServiceLocatorWeb.getLookupService();
530 }
531 return this.lookupService;
532 }
533
534 public void setLookupService(LookupService lookupService) {
535 this.lookupService = lookupService;
536 }
537
538 protected DataObjectAuthorizationService getDataObjectAuthorizationService() {
539 if (dataObjectAuthorizationService == null) {
540 this.dataObjectAuthorizationService = KRADServiceLocatorWeb.getDataObjectAuthorizationService();
541 }
542 return dataObjectAuthorizationService;
543 }
544
545 public void setDataObjectAuthorizationService(DataObjectAuthorizationService dataObjectAuthorizationService) {
546 this.dataObjectAuthorizationService = dataObjectAuthorizationService;
547 }
548
549 protected DataObjectMetaDataService getDataObjectMetaDataService() {
550 if (dataObjectMetaDataService == null) {
551 this.dataObjectMetaDataService = KRADServiceLocatorWeb.getDataObjectMetaDataService();
552 }
553 return dataObjectMetaDataService;
554 }
555
556 public void setDataObjectMetaDataService(DataObjectMetaDataService dataObjectMetaDataService) {
557 this.dataObjectMetaDataService = dataObjectMetaDataService;
558 }
559
560 public DocumentDictionaryService getDocumentDictionaryService() {
561 if (documentDictionaryService == null) {
562 this.documentDictionaryService = KRADServiceLocatorWeb.getDocumentDictionaryService();
563 }
564 return documentDictionaryService;
565 }
566
567 public void setDocumentDictionaryService(DocumentDictionaryService documentDictionaryService) {
568 this.documentDictionaryService = documentDictionaryService;
569 }
570
571 protected EncryptionService getEncryptionService() {
572 if (encryptionService == null) {
573 encryptionService = CoreApiServiceLocator.getEncryptionService();
574 }
575 return encryptionService;
576 }
577
578 public void setEncryptionService(EncryptionService encryptionService) {
579 this.encryptionService = encryptionService;
580 }
581
582 protected BusinessObjectService getBusinessObjectService() {
583 if (businessObjectService == null) {
584 businessObjectService = KRADServiceLocator.getBusinessObjectService();
585 }
586 return businessObjectService;
587 }
588
589 public void setBusinessObjectService(BusinessObjectService businessObjectService) {
590 this.businessObjectService = businessObjectService;
591 }
592
593 protected MaintenanceDocumentService getMaintenanceDocumentService() {
594 if (maintenanceDocumentService == null) {
595 maintenanceDocumentService = KRADServiceLocatorWeb.getMaintenanceDocumentService();
596 }
597 return maintenanceDocumentService;
598 }
599
600 public void setMaintenanceDocumentService(MaintenanceDocumentService maintenanceDocumentService) {
601 this.maintenanceDocumentService = maintenanceDocumentService;
602 }
603 }