View Javadoc

1   /**
2    * Copyright 2005-2013 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 BusinessObjectService businessObjectService;
74      private transient MaintenanceDocumentService maintenanceDocumentService;
75  
76      /**
77       * @see org.kuali.rice.krad.maintenance.Maintainable#retrieveObjectForEditOrCopy(MaintenanceDocument, java.util.Map)
78       */
79      @Override
80      public Object retrieveObjectForEditOrCopy(MaintenanceDocument document, Map<String, String> dataObjectKeys) {
81          Object dataObject = null;
82  
83          try {
84              dataObject = getLookupService().findObjectBySearch(getDataObjectClass(), dataObjectKeys);
85          } catch (ClassNotPersistenceCapableException ex) {
86              if (!document.getOldMaintainableObject().isExternalBusinessObject()) {
87                  throw new RuntimeException("Data Object Class: "
88                          + getDataObjectClass()
89                          + " is not persistable and is not externalizable - configuration error");
90              }
91              // otherwise, let fall through
92          }
93  
94          return dataObject;
95      }
96  
97      /**
98       * @see org.kuali.rice.krad.maintenance.Maintainable#setDocumentNumber
99       */
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                     fieldValue = getEncryptionService().encrypt(fieldValue);
215                 } catch (GeneralSecurityException e) {
216                     LOG.error("Unable to encrypt secure field for locking representation " + e.getMessage());
217                     throw new RuntimeException(
218                             "Unable to encrypt secure field for locking representation " + e.getMessage());
219                 }
220             }
221 
222             lockRepresentation.append(fieldName);
223             lockRepresentation.append(KRADConstants.Maintenance.LOCK_AFTER_FIELDNAME_DELIM);
224             lockRepresentation.append(String.valueOf(fieldValue));
225             if (i.hasNext()) {
226                 lockRepresentation.append(KRADConstants.Maintenance.LOCK_AFTER_VALUE_DELIM);
227             }
228         }
229 
230         MaintenanceLock maintenanceLock = new MaintenanceLock();
231         maintenanceLock.setDocumentNumber(documentNumber);
232         maintenanceLock.setLockingRepresentation(lockRepresentation.toString());
233         maintenanceLocks.add(maintenanceLock);
234 
235         return maintenanceLocks;
236     }
237 
238     /**
239      * Retrieves the document type name from the data dictionary based on
240      * business object class
241      */
242     protected String getDocumentTypeName() {
243         return getDocumentDictionaryService().getMaintenanceDocumentTypeName(dataObjectClass);
244     }
245 
246     /**
247      * @see org.kuali.rice.krad.maintenance.Maintainable#saveDataObject
248      */
249     @Override
250     public void saveDataObject() {
251         if (dataObject instanceof PersistableBusinessObject) {
252             getBusinessObjectService().linkAndSave((PersistableBusinessObject) dataObject);
253         } else {
254             throw new RuntimeException(
255                     "Cannot save object of type: " + dataObjectClass + " with business object service");
256         }
257     }
258 
259     /**
260      * @see org.kuali.rice.krad.maintenance.Maintainable#deleteDataObject
261      */
262     @Override
263     public void deleteDataObject() {
264         if (dataObject == null) {
265             return;
266         }
267 
268         if (dataObject instanceof PersistableBusinessObject) {
269             getBusinessObjectService().delete((PersistableBusinessObject) dataObject);
270             dataObject = null;
271         } else {
272             throw new RuntimeException(
273                     "Cannot delete object of type: " + dataObjectClass + " with business object service");
274         }
275     }
276 
277     /**
278      * @see org.kuali.rice.krad.maintenance.Maintainable#doRouteStatusChange
279      */
280     @Override
281     public void doRouteStatusChange(DocumentHeader documentHeader) {
282         // no default implementation
283     }
284 
285     /**
286      * @see org.kuali.rice.krad.maintenance.Maintainable#getLockingDocumentId
287      */
288     @Override
289     public String getLockingDocumentId() {
290         return getMaintenanceDocumentService().getLockingDocumentId(this, documentNumber);
291     }
292 
293     /**
294      * @see org.kuali.rice.krad.maintenance.Maintainable#getWorkflowEngineDocumentIdsToLock
295      */
296     @Override
297     public List<String> getWorkflowEngineDocumentIdsToLock() {
298         return null;
299     }
300 
301     /**
302      * Default implementation simply returns false to indicate that custom
303      * lock descriptors are not supported by MaintainableImpl. If custom
304      * lock descriptors are needed, the appropriate subclasses should override
305      * this method
306      *
307      * @see org.kuali.rice.krad.maintenance.Maintainable#useCustomLockDescriptors
308      */
309     @Override
310     public boolean useCustomLockDescriptors() {
311         return false;
312     }
313 
314     /**
315      * Default implementation just throws a PessimisticLockingException.
316      * Subclasses of MaintainableImpl that need support for custom lock
317      * descriptors should override this method
318      *
319      * @see org.kuali.rice.krad.maintenance.Maintainable#getCustomLockDescriptor
320      */
321     @Override
322     public String getCustomLockDescriptor(Person user) {
323         throw new PessimisticLockingException("The Maintainable for document " + documentNumber +
324                 " is using pessimistic locking with custom lock descriptors, but the Maintainable has not overridden the getCustomLockDescriptor method");
325     }
326 
327     /**
328      * @see org.kuali.rice.krad.maintenance.Maintainable#isNotesEnabled
329      */
330     @Override
331     public boolean isNotesEnabled() {
332         return getDataObjectMetaDataService().areNotesSupported(dataObjectClass);
333     }
334 
335     /**
336      * @see org.kuali.rice.krad.maintenance.MaintainableImpl#isExternalBusinessObject
337      */
338     @Override
339     public boolean isExternalBusinessObject() {
340         return false;
341     }
342 
343     /**
344      * @see org.kuali.rice.krad.maintenance.MaintainableImpl#prepareExternalBusinessObject
345      */
346     @Override
347     public void prepareExternalBusinessObject(BusinessObject businessObject) {
348         // by default do nothing
349     }
350 
351     /**
352      * Checks whether the data object is not null and has its primary key values populated
353      *
354      * @see org.kuali.rice.krad.maintenance.MaintainableImpl#isOldDataObjectInDocument
355      */
356     @Override
357     public boolean isOldDataObjectInDocument() {
358         boolean isOldDataObjectInExistence = true;
359 
360         if (getDataObject() == null) {
361             isOldDataObjectInExistence = false;
362         } else {
363             Map<String, ?> keyFieldValues = getDataObjectMetaDataService().getPrimaryKeyFieldValues(getDataObject());
364             for (Object keyValue : keyFieldValues.values()) {
365                 if (keyValue == null) {
366                     isOldDataObjectInExistence = false;
367                 } else if ((keyValue instanceof String) && StringUtils.isBlank((String) keyValue)) {
368                     isOldDataObjectInExistence = false;
369                 }
370 
371                 if (!isOldDataObjectInExistence) {
372                     break;
373                 }
374             }
375         }
376 
377         return isOldDataObjectInExistence;
378     }
379 
380     /**
381      * @see org.kuali.rice.krad.maintenance.Maintainable#prepareForSave
382      */
383     @Override
384     public void prepareForSave() {
385         // by default do nothing
386     }
387 
388     /**
389      * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterRetrieve
390      */
391     @Override
392     public void processAfterRetrieve() {
393         // by default do nothing
394     }
395 
396     /**
397      * @see org.kuali.rice.krad.maintenance.MaintainableImpl#setupNewFromExisting
398      */
399     @Override
400     public void setupNewFromExisting(MaintenanceDocument document, Map<String, String[]> parameters) {
401         // by default do nothing
402     }
403 
404     /**
405      * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterCopy
406      */
407     @Override
408     public void processAfterCopy(MaintenanceDocument document, Map<String, String[]> requestParameters) {
409         // by default do nothing
410     }
411 
412     /**
413      * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterEdit
414      */
415     @Override
416     public void processAfterEdit(MaintenanceDocument document, Map<String, String[]> requestParameters) {
417         // by default do nothing
418     }
419 
420     /**
421      * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterNew
422      */
423     @Override
424     public void processAfterNew(MaintenanceDocument document, Map<String, String[]> requestParameters) {
425         // by default do nothing
426     }
427 
428     /**
429      * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterPost
430      */
431     @Override
432     public void processAfterPost(MaintenanceDocument document, Map<String, String[]> requestParameters) {
433         // by default do nothing
434     }
435 
436     /**
437      * In the case of edit maintenance adds a new blank line to the old side
438      *
439      * TODO: should this write some sort of missing message on the old side
440      * instead?
441      *
442      * @see org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl#processAfterAddLine(org.kuali.rice.krad.uif.view.View,
443      *      org.kuali.rice.krad.uif.container.CollectionGroup, java.lang.Object,
444      *      java.lang.Object)
445      */
446     @Override
447     protected void processAfterAddLine(View view, CollectionGroup collectionGroup, Object model, Object addLine) {
448         super.processAfterAddLine(view, collectionGroup, model, addLine);
449 
450         // Check for maintenance documents in edit but exclude notes and ad hoc recipients
451         if (model instanceof MaintenanceDocumentForm
452                 && KRADConstants.MAINTENANCE_EDIT_ACTION.equals(((MaintenanceDocumentForm)model).getMaintenanceAction()) && !(addLine instanceof Note) && !(addLine instanceof AdHocRoutePerson) && !(addLine instanceof AdHocRouteWorkgroup)) {
453             MaintenanceDocumentForm maintenanceForm = (MaintenanceDocumentForm) model;
454             MaintenanceDocument document = maintenanceForm.getDocument();
455 
456             // get the old object's collection
457             //KULRICE-7970 support multiple level objects
458             String bindingPrefix = collectionGroup.getBindingInfo().getBindByNamePrefix();
459             String propertyPath = collectionGroup.getPropertyName();
460             if(bindingPrefix!=""&&bindingPrefix!= null)     {
461                 propertyPath = bindingPrefix + "." + propertyPath;
462             }
463 
464             Collection<Object> oldCollection = ObjectPropertyUtils
465                     .getPropertyValue(document.getOldMaintainableObject().getDataObject(),
466                             propertyPath);
467 
468 
469             try {
470                 Object blankLine = collectionGroup.getCollectionObjectClass().newInstance();
471                 //Add a blank line to the top of the collection
472                 if(oldCollection instanceof List){
473                    ((List) oldCollection).add(0,blankLine);
474                 } else {
475                     oldCollection.add(blankLine);
476                 }
477             } catch (Exception e) {
478                 throw new RuntimeException("Unable to create new line instance for old maintenance object", e);
479             }
480         }
481     }
482 
483     /**
484      * In the case of edit maintenance deleted the item on the old side
485      *
486      *
487      * @see org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl#processAfterDeleteLine(View,
488      *      org.kuali.rice.krad.uif.container.CollectionGroup, java.lang.Object,  int)
489      */
490     @Override
491     protected void processAfterDeleteLine(View view, CollectionGroup collectionGroup, Object model, int lineIndex) {
492         super.processAfterDeleteLine(view, collectionGroup, model, lineIndex);
493 
494         // Check for maintenance documents in edit but exclude notes and ad hoc recipients
495         if (model instanceof MaintenanceDocumentForm
496                 && KRADConstants.MAINTENANCE_EDIT_ACTION.equals(((MaintenanceDocumentForm)model).getMaintenanceAction())
497                 && !collectionGroup.getCollectionObjectClass().getName().equals(Note.class.getName())
498                 && !collectionGroup.getCollectionObjectClass().getName().equals(AdHocRoutePerson.class.getName())
499                 && !collectionGroup.getCollectionObjectClass().getName().equals(AdHocRouteWorkgroup.class.getName())) {
500             MaintenanceDocumentForm maintenanceForm = (MaintenanceDocumentForm) model;
501             MaintenanceDocument document = maintenanceForm.getDocument();
502 
503             // get the old object's collection
504             Collection<Object> oldCollection = ObjectPropertyUtils
505                     .getPropertyValue(document.getOldMaintainableObject().getDataObject(),
506                             collectionGroup.getPropertyName());
507             try {
508                 // Remove the object at lineIndex from the collection
509                 oldCollection.remove(oldCollection.toArray()[lineIndex]);
510             } catch (Exception e) {
511                 throw new RuntimeException("Unable to delete line instance for old maintenance object", e);
512             }
513         }
514     }
515 
516     /**
517      * Retrieves the document number configured on this maintainable
518      *
519      * @return String document number
520      */
521     protected String getDocumentNumber() {
522         return this.documentNumber;
523     }
524 
525     protected LookupService getLookupService() {
526         if (lookupService == null) {
527             lookupService = KRADServiceLocatorWeb.getLookupService();
528         }
529         return this.lookupService;
530     }
531 
532     public void setLookupService(LookupService lookupService) {
533         this.lookupService = lookupService;
534     }
535 
536     protected DataObjectAuthorizationService getDataObjectAuthorizationService() {
537         if (dataObjectAuthorizationService == null) {
538             this.dataObjectAuthorizationService = KRADServiceLocatorWeb.getDataObjectAuthorizationService();
539         }
540         return dataObjectAuthorizationService;
541     }
542 
543     public void setDataObjectAuthorizationService(DataObjectAuthorizationService dataObjectAuthorizationService) {
544         this.dataObjectAuthorizationService = dataObjectAuthorizationService;
545     }
546 
547     protected DataObjectMetaDataService getDataObjectMetaDataService() {
548         if (dataObjectMetaDataService == null) {
549             this.dataObjectMetaDataService = KRADServiceLocatorWeb.getDataObjectMetaDataService();
550         }
551         return dataObjectMetaDataService;
552     }
553 
554     public void setDataObjectMetaDataService(DataObjectMetaDataService dataObjectMetaDataService) {
555         this.dataObjectMetaDataService = dataObjectMetaDataService;
556     }
557 
558     public DocumentDictionaryService getDocumentDictionaryService() {
559         if (documentDictionaryService == null) {
560             this.documentDictionaryService = KRADServiceLocatorWeb.getDocumentDictionaryService();
561         }
562         return documentDictionaryService;
563     }
564 
565     public void setDocumentDictionaryService(DocumentDictionaryService documentDictionaryService) {
566         this.documentDictionaryService = documentDictionaryService;
567     }
568 
569     protected EncryptionService getEncryptionService() {
570         if (encryptionService == null) {
571             encryptionService = CoreApiServiceLocator.getEncryptionService();
572         }
573         return encryptionService;
574     }
575 
576     public void setEncryptionService(EncryptionService encryptionService) {
577         this.encryptionService = encryptionService;
578     }
579 
580     protected BusinessObjectService getBusinessObjectService() {
581         if (businessObjectService == null) {
582             businessObjectService = KRADServiceLocator.getBusinessObjectService();
583         }
584         return businessObjectService;
585     }
586 
587     public void setBusinessObjectService(BusinessObjectService businessObjectService) {
588         this.businessObjectService = businessObjectService;
589     }
590 
591     protected MaintenanceDocumentService getMaintenanceDocumentService() {
592         if (maintenanceDocumentService == null) {
593             maintenanceDocumentService = KRADServiceLocatorWeb.getMaintenanceDocumentService();
594         }
595         return maintenanceDocumentService;
596     }
597 
598     public void setMaintenanceDocumentService(MaintenanceDocumentService maintenanceDocumentService) {
599         this.maintenanceDocumentService = maintenanceDocumentService;
600     }
601 }