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                     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 }