View Javadoc

1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.maintenance;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException;
20  import org.kuali.rice.core.api.CoreApiServiceLocator;
21  import org.kuali.rice.core.api.encryption.EncryptionService;
22  import org.kuali.rice.kim.api.identity.Person;
23  import org.kuali.rice.krad.bo.AdHocRoutePerson;
24  import org.kuali.rice.krad.bo.AdHocRouteWorkgroup;
25  import org.kuali.rice.krad.bo.BusinessObject;
26  import org.kuali.rice.krad.bo.DocumentHeader;
27  import org.kuali.rice.krad.bo.Note;
28  import org.kuali.rice.krad.bo.PersistableBusinessObject;
29  import org.kuali.rice.krad.exception.PessimisticLockingException;
30  import org.kuali.rice.krad.service.BusinessObjectService;
31  import org.kuali.rice.krad.service.DataObjectAuthorizationService;
32  import org.kuali.rice.krad.service.DataObjectMetaDataService;
33  import org.kuali.rice.krad.service.DocumentDictionaryService;
34  import org.kuali.rice.krad.service.KRADServiceLocator;
35  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
36  import org.kuali.rice.krad.service.LookupService;
37  import org.kuali.rice.krad.service.MaintenanceDocumentService;
38  import org.kuali.rice.krad.uif.container.CollectionGroup;
39  import org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl;
40  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
41  import org.kuali.rice.krad.uif.view.View;
42  import org.kuali.rice.krad.util.KRADConstants;
43  import org.kuali.rice.krad.util.ObjectUtils;
44  import org.kuali.rice.krad.web.form.MaintenanceDocumentForm;
45  
46  import java.security.GeneralSecurityException;
47  import java.util.ArrayList;
48  import java.util.Collection;
49  import java.util.Iterator;
50  import java.util.List;
51  import java.util.Map;
52  
53  /**
54   * Default implementation of the <code>Maintainable</code> interface
55   *
56   * @author Kuali Rice Team (rice.collab@kuali.org)
57   */
58  public class MaintainableImpl extends ViewHelperServiceImpl implements Maintainable {
59      private static final long serialVersionUID = 9125271369161634992L;
60  
61      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(MaintainableImpl.class);
62  
63      private String documentNumber;
64      private Object dataObject;
65      private Class<?> dataObjectClass;
66      private String maintenanceAction;
67  
68      private transient LookupService lookupService;
69      private transient DataObjectAuthorizationService dataObjectAuthorizationService;
70      private transient DataObjectMetaDataService dataObjectMetaDataService;
71      private transient DocumentDictionaryService documentDictionaryService;
72      private transient EncryptionService encryptionService;
73      private transient MaintenanceDocumentService maintenanceDocumentService;
74  
75      /**
76       * @see org.kuali.rice.krad.maintenance.Maintainable#retrieveObjectForEditOrCopy(MaintenanceDocument, java.util.Map)
77       */
78      @Override
79      public Object retrieveObjectForEditOrCopy(MaintenanceDocument document, Map<String, String> dataObjectKeys) {
80          Object dataObject = null;
81  
82          try {
83              dataObject = getLookupService().findObjectBySearch(getDataObjectClass(), dataObjectKeys);
84          } catch (ClassNotPersistenceCapableException ex) {
85              if (!document.getOldMaintainableObject().isExternalBusinessObject()) {
86                  throw new RuntimeException("Data Object Class: "
87                          + getDataObjectClass()
88                          + " is not persistable and is not externalizable - configuration error");
89              }
90              // otherwise, let fall through
91          }
92  
93          return dataObject;
94      }
95  
96      /**
97       * @see org.kuali.rice.krad.maintenance.Maintainable#setDocumentNumber
98       */
99      @Override
100     public void setDocumentNumber(String documentNumber) {
101         this.documentNumber = documentNumber;
102     }
103 
104     /**
105      * @see org.kuali.rice.krad.maintenance.Maintainable#getDocumentTitle
106      */
107     @Override
108     public String getDocumentTitle(MaintenanceDocument document) {
109         // default implementation is to allow MaintenanceDocumentBase to
110         // generate the doc title
111         return "";
112     }
113 
114     /**
115      * @see org.kuali.rice.krad.maintenance.Maintainable#getDataObject
116      */
117     @Override
118     public Object getDataObject() {
119         return dataObject;
120     }
121 
122     /**
123      * @see org.kuali.rice.krad.maintenance.Maintainable#setDataObject
124      */
125     @Override
126     public void setDataObject(Object object) {
127         this.dataObject = object;
128     }
129 
130     /**
131      * @see org.kuali.rice.krad.maintenance.Maintainable#getDataObjectClass
132      */
133     @Override
134     public Class getDataObjectClass() {
135         return dataObjectClass;
136     }
137 
138     /**
139      * @see org.kuali.rice.krad.maintenance.Maintainable#setDataObjectClass
140      */
141     @Override
142     public void setDataObjectClass(Class dataObjectClass) {
143         this.dataObjectClass = dataObjectClass;
144     }
145 
146     /**
147      * Persistable business objects are lockable
148      *
149      * @see org.kuali.rice.krad.maintenance.Maintainable#isLockable
150      */
151     @Override
152     public boolean isLockable() {
153         return KRADServiceLocator.getPersistenceStructureService().isPersistable(getDataObject().getClass());
154     }
155 
156     /**
157      * Returns the data object if its persistable, null otherwise
158      *
159      * @see org.kuali.rice.krad.maintenance.Maintainable#getPersistableBusinessObject
160      */
161     @Override
162     public PersistableBusinessObject getPersistableBusinessObject() {
163         if (KRADServiceLocator.getPersistenceStructureService().isPersistable(getDataObject().getClass())) {
164             return (PersistableBusinessObject) getDataObject();
165         } else {
166             return null;
167         }
168 
169     }
170 
171     /**
172      * @see org.kuali.rice.krad.maintenance.Maintainable#getMaintenanceAction
173      */
174     @Override
175     public String getMaintenanceAction() {
176         return maintenanceAction;
177     }
178 
179     /**
180      * @see org.kuali.rice.krad.maintenance.Maintainable#setMaintenanceAction
181      */
182     @Override
183     public void setMaintenanceAction(String maintenanceAction) {
184         this.maintenanceAction = maintenanceAction;
185     }
186 
187     /**
188      * Note: as currently implemented, every key field for a given
189      * data object class must have a visible getter
190      *
191      * @see org.kuali.rice.krad.maintenance.Maintainable#generateMaintenanceLocks
192      */
193     @Override
194     public List<MaintenanceLock> generateMaintenanceLocks() {
195         List<MaintenanceLock> maintenanceLocks = new ArrayList<MaintenanceLock>();
196         StringBuffer lockRepresentation = new StringBuffer(dataObjectClass.getName());
197         lockRepresentation.append(KRADConstants.Maintenance.LOCK_AFTER_CLASS_DELIM);
198 
199         Object bo = getDataObject();
200         List keyFieldNames = getDocumentDictionaryService().getLockingKeys(getDocumentTypeName());
201 
202         for (Iterator i = keyFieldNames.iterator(); i.hasNext(); ) {
203             String fieldName = (String) i.next();
204             Object fieldValue = ObjectUtils.getPropertyValue(bo, fieldName);
205             if (fieldValue == null) {
206                 fieldValue = "";
207             }
208 
209             // check if field is a secure
210             if (getDataObjectAuthorizationService()
211                     .attributeValueNeedsToBeEncryptedOnFormsAndLinks(dataObjectClass, fieldName)) {
212                 try {
213                     if(CoreApiServiceLocator.getEncryptionService().isEnabled()) {
214                         fieldValue = getEncryptionService().encrypt(fieldValue);
215                     }
216                 } catch (GeneralSecurityException e) {
217                     LOG.error("Unable to encrypt secure field for locking representation " + e.getMessage());
218                     throw new RuntimeException(
219                             "Unable to encrypt secure field for locking representation " + e.getMessage());
220                 }
221             }
222 
223             lockRepresentation.append(fieldName);
224             lockRepresentation.append(KRADConstants.Maintenance.LOCK_AFTER_FIELDNAME_DELIM);
225             lockRepresentation.append(String.valueOf(fieldValue));
226             if (i.hasNext()) {
227                 lockRepresentation.append(KRADConstants.Maintenance.LOCK_AFTER_VALUE_DELIM);
228             }
229         }
230 
231         MaintenanceLock maintenanceLock = new MaintenanceLock();
232         maintenanceLock.setDocumentNumber(documentNumber);
233         maintenanceLock.setLockingRepresentation(lockRepresentation.toString());
234         maintenanceLocks.add(maintenanceLock);
235 
236         return maintenanceLocks;
237     }
238 
239     /**
240      * Retrieves the document type name from the data dictionary based on
241      * business object class
242      */
243     protected String getDocumentTypeName() {
244         return getDocumentDictionaryService().getMaintenanceDocumentTypeName(dataObjectClass);
245     }
246 
247     /**
248      * @see org.kuali.rice.krad.maintenance.Maintainable#saveDataObject
249      */
250     @Override
251     public void saveDataObject() {
252         if (dataObject instanceof PersistableBusinessObject) {
253             getBusinessObjectService().linkAndSave((PersistableBusinessObject) dataObject);
254         } else {
255             throw new RuntimeException(
256                     "Cannot save object of type: " + dataObjectClass + " with business object service");
257         }
258     }
259 
260     /**
261      * @see org.kuali.rice.krad.maintenance.Maintainable#deleteDataObject
262      */
263     @Override
264     public void deleteDataObject() {
265         if (dataObject == null) {
266             return;
267         }
268 
269         if (dataObject instanceof PersistableBusinessObject) {
270             getBusinessObjectService().delete((PersistableBusinessObject) dataObject);
271             dataObject = null;
272         } else {
273             throw new RuntimeException(
274                     "Cannot delete object of type: " + dataObjectClass + " with business object service");
275         }
276     }
277 
278     /**
279      * @see org.kuali.rice.krad.maintenance.Maintainable#doRouteStatusChange
280      */
281     @Override
282     public void doRouteStatusChange(DocumentHeader documentHeader) {
283         // no default implementation
284     }
285 
286     /**
287      * @see org.kuali.rice.krad.maintenance.Maintainable#getLockingDocumentId
288      */
289     @Override
290     public String getLockingDocumentId() {
291         return getMaintenanceDocumentService().getLockingDocumentId(this, documentNumber);
292     }
293 
294     /**
295      * @see org.kuali.rice.krad.maintenance.Maintainable#getWorkflowEngineDocumentIdsToLock
296      */
297     @Override
298     public List<String> getWorkflowEngineDocumentIdsToLock() {
299         return null;
300     }
301 
302     /**
303      * Default implementation simply returns false to indicate that custom
304      * lock descriptors are not supported by MaintainableImpl. If custom
305      * lock descriptors are needed, the appropriate subclasses should override
306      * this method
307      *
308      * @see org.kuali.rice.krad.maintenance.Maintainable#useCustomLockDescriptors
309      */
310     @Override
311     public boolean useCustomLockDescriptors() {
312         return false;
313     }
314 
315     /**
316      * Default implementation just throws a PessimisticLockingException.
317      * Subclasses of MaintainableImpl that need support for custom lock
318      * descriptors should override this method
319      *
320      * @see org.kuali.rice.krad.maintenance.Maintainable#getCustomLockDescriptor
321      */
322     @Override
323     public String getCustomLockDescriptor(Person user) {
324         throw new PessimisticLockingException("The Maintainable for document " + documentNumber +
325                 " is using pessimistic locking with custom lock descriptors, but the Maintainable has not overridden the getCustomLockDescriptor method");
326     }
327 
328     /**
329      * @see org.kuali.rice.krad.maintenance.Maintainable#isNotesEnabled
330      */
331     @Override
332     public boolean isNotesEnabled() {
333         return getDataObjectMetaDataService().areNotesSupported(dataObjectClass);
334     }
335 
336     /**
337      * @see org.kuali.rice.krad.maintenance.MaintainableImpl#isExternalBusinessObject
338      */
339     @Override
340     public boolean isExternalBusinessObject() {
341         return false;
342     }
343 
344     /**
345      * @see org.kuali.rice.krad.maintenance.MaintainableImpl#prepareExternalBusinessObject
346      */
347     @Override
348     public void prepareExternalBusinessObject(BusinessObject businessObject) {
349         // by default do nothing
350     }
351 
352     /**
353      * Checks whether the data object is not null and has its primary key values populated
354      *
355      * @see org.kuali.rice.krad.maintenance.MaintainableImpl#isOldDataObjectInDocument
356      */
357     @Override
358     public boolean isOldDataObjectInDocument() {
359         boolean isOldDataObjectInExistence = true;
360 
361         if (getDataObject() == null) {
362             isOldDataObjectInExistence = false;
363         } else {
364             Map<String, ?> keyFieldValues = getDataObjectMetaDataService().getPrimaryKeyFieldValues(getDataObject());
365             for (Object keyValue : keyFieldValues.values()) {
366                 if (keyValue == null) {
367                     isOldDataObjectInExistence = false;
368                 } else if ((keyValue instanceof String) && StringUtils.isBlank((String) keyValue)) {
369                     isOldDataObjectInExistence = false;
370                 }
371 
372                 if (!isOldDataObjectInExistence) {
373                     break;
374                 }
375             }
376         }
377 
378         return isOldDataObjectInExistence;
379     }
380 
381     /**
382      * @see org.kuali.rice.krad.maintenance.Maintainable#prepareForSave
383      */
384     @Override
385     public void prepareForSave() {
386         // by default do nothing
387     }
388 
389     /**
390      * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterRetrieve
391      */
392     @Override
393     public void processAfterRetrieve() {
394         // by default do nothing
395     }
396 
397     /**
398      * @see org.kuali.rice.krad.maintenance.MaintainableImpl#setupNewFromExisting
399      */
400     @Override
401     public void setupNewFromExisting(MaintenanceDocument document, Map<String, String[]> parameters) {
402         // by default do nothing
403     }
404 
405     /**
406      * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterCopy
407      */
408     @Override
409     public void processAfterCopy(MaintenanceDocument document, Map<String, String[]> requestParameters) {
410         // by default do nothing
411     }
412 
413     /**
414      * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterEdit
415      */
416     @Override
417     public void processAfterEdit(MaintenanceDocument document, Map<String, String[]> requestParameters) {
418         // by default do nothing
419     }
420 
421     /**
422      * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterNew
423      */
424     @Override
425     public void processAfterNew(MaintenanceDocument document, Map<String, String[]> requestParameters) {
426         // by default do nothing
427     }
428 
429     /**
430      * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterPost
431      */
432     @Override
433     public void processAfterPost(MaintenanceDocument document, Map<String, String[]> requestParameters) {
434         // by default do nothing
435     }
436 
437     /**
438      * In the case of edit maintenance adds a new blank line to the old side
439      *
440      * TODO: should this write some sort of missing message on the old side
441      * instead?
442      *
443      * @see org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl#processAfterAddLine(org.kuali.rice.krad.uif.view.View,
444      *      org.kuali.rice.krad.uif.container.CollectionGroup, java.lang.Object,
445      *      java.lang.Object)
446      */
447     @Override
448     protected void processAfterAddLine(View view, CollectionGroup collectionGroup, Object model, Object addLine,
449             boolean isValidLine) {
450         super.processAfterAddLine(view, collectionGroup, model, addLine, isValidLine);
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(
455                 ((MaintenanceDocumentForm) model).getMaintenanceAction())
456                 && !(addLine instanceof Note)
457                 && !(addLine instanceof AdHocRoutePerson)
458                 && !(addLine instanceof AdHocRouteWorkgroup)) {
459             MaintenanceDocumentForm maintenanceForm = (MaintenanceDocumentForm) model;
460             MaintenanceDocument document = maintenanceForm.getDocument();
461 
462             // get the old object's collection
463             //KULRICE-7970 support multiple level objects
464             String bindingPrefix = collectionGroup.getBindingInfo().getBindByNamePrefix();
465             String propertyPath = collectionGroup.getPropertyName();
466             if (bindingPrefix != "" && bindingPrefix != null) {
467                 propertyPath = bindingPrefix + "." + propertyPath;
468             }
469 
470             Collection<Object> oldCollection = ObjectPropertyUtils.getPropertyValue(
471                     document.getOldMaintainableObject().getDataObject(), propertyPath);
472 
473             try {
474                 Object blankLine = collectionGroup.getCollectionObjectClass().newInstance();
475                 //Add a blank line to the top of the collection
476                 if (oldCollection instanceof List) {
477                     ((List) oldCollection).add(0, blankLine);
478                 } else {
479                     oldCollection.add(blankLine);
480                 }
481             } catch (Exception e) {
482                 throw new RuntimeException("Unable to create new line instance for old maintenance object", e);
483             }
484         }
485     }
486 
487     /**
488      * In the case of edit maintenance deleted the item on the old side
489      *
490      *
491      * @see org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl#processAfterDeleteLine(View,
492      *      org.kuali.rice.krad.uif.container.CollectionGroup, java.lang.Object,  int)
493      */
494     @Override
495     protected void processAfterDeleteLine(View view, CollectionGroup collectionGroup, Object model, int lineIndex) {
496         super.processAfterDeleteLine(view, collectionGroup, model, lineIndex);
497 
498         // Check for maintenance documents in edit but exclude notes and ad hoc recipients
499         if (model instanceof MaintenanceDocumentForm
500                 && KRADConstants.MAINTENANCE_EDIT_ACTION.equals(((MaintenanceDocumentForm)model).getMaintenanceAction())
501                 && !collectionGroup.getCollectionObjectClass().getName().equals(Note.class.getName())
502                 && !collectionGroup.getCollectionObjectClass().getName().equals(AdHocRoutePerson.class.getName())
503                 && !collectionGroup.getCollectionObjectClass().getName().equals(AdHocRouteWorkgroup.class.getName())) {
504             MaintenanceDocumentForm maintenanceForm = (MaintenanceDocumentForm) model;
505             MaintenanceDocument document = maintenanceForm.getDocument();
506 
507             // get the old object's collection
508             Collection<Object> oldCollection = ObjectPropertyUtils
509                     .getPropertyValue(document.getOldMaintainableObject().getDataObject(),
510                             collectionGroup.getPropertyName());
511             try {
512                 // Remove the object at lineIndex from the collection
513                 oldCollection.remove(oldCollection.toArray()[lineIndex]);
514             } catch (Exception e) {
515                 throw new RuntimeException("Unable to delete line instance for old maintenance object", e);
516             }
517         }
518     }
519 
520     /**
521      * Retrieves the document number configured on this maintainable
522      *
523      * @return String document number
524      */
525     protected String getDocumentNumber() {
526         return this.documentNumber;
527     }
528 
529     protected LookupService getLookupService() {
530         if (lookupService == null) {
531             lookupService = KRADServiceLocatorWeb.getLookupService();
532         }
533         return this.lookupService;
534     }
535 
536     public void setLookupService(LookupService lookupService) {
537         this.lookupService = lookupService;
538     }
539 
540     protected DataObjectAuthorizationService getDataObjectAuthorizationService() {
541         if (dataObjectAuthorizationService == null) {
542             this.dataObjectAuthorizationService = KRADServiceLocatorWeb.getDataObjectAuthorizationService();
543         }
544         return dataObjectAuthorizationService;
545     }
546 
547     public void setDataObjectAuthorizationService(DataObjectAuthorizationService dataObjectAuthorizationService) {
548         this.dataObjectAuthorizationService = dataObjectAuthorizationService;
549     }
550 
551     protected DataObjectMetaDataService getDataObjectMetaDataService() {
552         if (dataObjectMetaDataService == null) {
553             this.dataObjectMetaDataService = KRADServiceLocatorWeb.getDataObjectMetaDataService();
554         }
555         return dataObjectMetaDataService;
556     }
557 
558     public void setDataObjectMetaDataService(DataObjectMetaDataService dataObjectMetaDataService) {
559         this.dataObjectMetaDataService = dataObjectMetaDataService;
560     }
561 
562     public DocumentDictionaryService getDocumentDictionaryService() {
563         if (documentDictionaryService == null) {
564             this.documentDictionaryService = KRADServiceLocatorWeb.getDocumentDictionaryService();
565         }
566         return documentDictionaryService;
567     }
568 
569     public void setDocumentDictionaryService(DocumentDictionaryService documentDictionaryService) {
570         this.documentDictionaryService = documentDictionaryService;
571     }
572 
573     protected EncryptionService getEncryptionService() {
574         if (encryptionService == null) {
575             encryptionService = CoreApiServiceLocator.getEncryptionService();
576         }
577         return encryptionService;
578     }
579 
580     public void setEncryptionService(EncryptionService encryptionService) {
581         this.encryptionService = encryptionService;
582     }
583 
584     protected MaintenanceDocumentService getMaintenanceDocumentService() {
585         if (maintenanceDocumentService == null) {
586             maintenanceDocumentService = KRADServiceLocatorWeb.getMaintenanceDocumentService();
587         }
588         return maintenanceDocumentService;
589     }
590 
591     public void setMaintenanceDocumentService(MaintenanceDocumentService maintenanceDocumentService) {
592         this.maintenanceDocumentService = maintenanceDocumentService;
593     }
594 }