View Javadoc

1   /**
2    * Copyright 2005-2011 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.BusinessObject;
24  import org.kuali.rice.krad.bo.DocumentHeader;
25  import org.kuali.rice.krad.bo.Note;
26  import org.kuali.rice.krad.bo.PersistableBusinessObject;
27  import org.kuali.rice.krad.document.MaintenanceDocument;
28  import org.kuali.rice.krad.document.MaintenanceLock;
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.view.View;
40  import org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl;
41  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
42  import org.kuali.rice.krad.util.KRADConstants;
43  import org.kuali.rice.krad.util.ObjectUtils;
44  import org.kuali.rice.krad.web.form.MaintenanceForm;
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      * @see org.kuali.rice.krad.maintenance.Maintainable#getMaintenanceAction
149      */
150     @Override
151     public String getMaintenanceAction() {
152         return maintenanceAction;
153     }
154 
155     /**
156      * @see org.kuali.rice.krad.maintenance.Maintainable#setMaintenanceAction
157      */
158     @Override
159     public void setMaintenanceAction(String maintenanceAction) {
160         this.maintenanceAction = maintenanceAction;
161     }
162 
163     /**
164      * Note: as currently implemented, every key field for a given
165      * data object class must have a visible getter
166      *
167      * @see org.kuali.rice.krad.maintenance.Maintainable#generateMaintenanceLocks
168      */
169     @Override
170     public List<MaintenanceLock> generateMaintenanceLocks() {
171         List<MaintenanceLock> maintenanceLocks = new ArrayList<MaintenanceLock>();
172         StringBuffer lockRepresentation = new StringBuffer(dataObjectClass.getName());
173         lockRepresentation.append(KRADConstants.Maintenance.LOCK_AFTER_CLASS_DELIM);
174 
175         Object bo = getDataObject();
176         List keyFieldNames = getDocumentDictionaryService().getLockingKeys(getDocumentTypeName());
177 
178         for (Iterator i = keyFieldNames.iterator(); i.hasNext(); ) {
179             String fieldName = (String) i.next();
180             Object fieldValue = ObjectUtils.getPropertyValue(bo, fieldName);
181             if (fieldValue == null) {
182                 fieldValue = "";
183             }
184 
185             // check if field is a secure
186             if (getDataObjectAuthorizationService()
187                     .attributeValueNeedsToBeEncryptedOnFormsAndLinks(dataObjectClass, fieldName)) {
188                 try {
189                     fieldValue = getEncryptionService().encrypt(fieldValue);
190                 } catch (GeneralSecurityException e) {
191                     LOG.error("Unable to encrypt secure field for locking representation " + e.getMessage());
192                     throw new RuntimeException(
193                             "Unable to encrypt secure field for locking representation " + e.getMessage());
194                 }
195             }
196 
197             lockRepresentation.append(fieldName);
198             lockRepresentation.append(KRADConstants.Maintenance.LOCK_AFTER_FIELDNAME_DELIM);
199             lockRepresentation.append(String.valueOf(fieldValue));
200             if (i.hasNext()) {
201                 lockRepresentation.append(KRADConstants.Maintenance.LOCK_AFTER_VALUE_DELIM);
202             }
203         }
204 
205         MaintenanceLock maintenanceLock = new MaintenanceLock();
206         maintenanceLock.setDocumentNumber(documentNumber);
207         maintenanceLock.setLockingRepresentation(lockRepresentation.toString());
208         maintenanceLocks.add(maintenanceLock);
209 
210         return maintenanceLocks;
211     }
212 
213     /**
214      * Retrieves the document type name from the data dictionary based on
215      * business object class
216      */
217     protected String getDocumentTypeName() {
218         return getDocumentDictionaryService().getMaintenanceDocumentTypeName(dataObjectClass);
219     }
220 
221     /**
222      * @see org.kuali.rice.krad.maintenance.Maintainable#saveDataObject
223      */
224     @Override
225     public void saveDataObject() {
226         if (dataObject instanceof PersistableBusinessObject) {
227             getBusinessObjectService().linkAndSave((PersistableBusinessObject) dataObject);
228         } else {
229             throw new RuntimeException(
230                     "Cannot save object of type: " + dataObjectClass + " with business object service");
231         }
232     }
233 
234     /**
235      * @see org.kuali.rice.krad.maintenance.Maintainable#deleteDataObject
236      */
237     @Override
238     public void deleteDataObject() {
239         if (dataObject == null) {
240             return;
241         }
242 
243         if (dataObject instanceof PersistableBusinessObject) {
244             getBusinessObjectService().delete((PersistableBusinessObject) dataObject);
245             dataObject = null;
246         } else {
247             throw new RuntimeException(
248                     "Cannot delete object of type: " + dataObjectClass + " with business object service");
249         }
250     }
251 
252     /**
253      * @see org.kuali.rice.krad.maintenance.Maintainable#doRouteStatusChange
254      */
255     @Override
256     public void doRouteStatusChange(DocumentHeader documentHeader) {
257         // no default implementation
258     }
259 
260     /**
261      * @see org.kuali.rice.krad.maintenance.Maintainable#getLockingDocumentId
262      */
263     @Override
264     public String getLockingDocumentId() {
265         return getMaintenanceDocumentService().getLockingDocumentId(this, documentNumber);
266     }
267 
268     /**
269      * @see org.kuali.rice.krad.maintenance.Maintainable#getWorkflowEngineDocumentIdsToLock
270      */
271     @Override
272     public List<String> getWorkflowEngineDocumentIdsToLock() {
273         return null;
274     }
275 
276     /**
277      * Default implementation simply returns false to indicate that custom
278      * lock descriptors are not supported by MaintainableImpl. If custom
279      * lock descriptors are needed, the appropriate subclasses should override
280      * this method
281      *
282      * @see org.kuali.rice.krad.maintenance.Maintainable#useCustomLockDescriptors
283      */
284     @Override
285     public boolean useCustomLockDescriptors() {
286         return false;
287     }
288 
289     /**
290      * Default implementation just throws a PessimisticLockingException.
291      * Subclasses of MaintainableImpl that need support for custom lock
292      * descriptors should override this method
293      *
294      * @see org.kuali.rice.krad.maintenance.Maintainable#getCustomLockDescriptor
295      */
296     @Override
297     public String getCustomLockDescriptor(Person user) {
298         throw new PessimisticLockingException("The Maintainable for document " + documentNumber +
299                 " is using pessimistic locking with custom lock descriptors, but the Maintainable has not overridden the getCustomLockDescriptor method");
300     }
301 
302     /**
303      * @see org.kuali.rice.krad.maintenance.Maintainable#isNotesEnabled
304      */
305     @Override
306     public boolean isNotesEnabled() {
307         return getDataObjectMetaDataService().areNotesSupported(dataObjectClass);
308     }
309 
310     /**
311      * @see org.kuali.rice.krad.maintenance.MaintainableImpl#isExternalBusinessObject
312      */
313     @Override
314     public boolean isExternalBusinessObject() {
315         return false;
316     }
317 
318     /**
319      * @see org.kuali.rice.krad.maintenance.MaintainableImpl#prepareExternalBusinessObject
320      */
321     @Override
322     public void prepareExternalBusinessObject(BusinessObject businessObject) {
323         // by default do nothing
324     }
325 
326     /**
327      * Checks whether the data object is not null and has its primary key values populated
328      *
329      * @see org.kuali.rice.krad.maintenance.MaintainableImpl#isOldDataObjectInDocument
330      */
331     @Override
332     public boolean isOldDataObjectInDocument() {
333         boolean isOldDataObjectInExistence = true;
334 
335         if (getDataObject() == null) {
336             isOldDataObjectInExistence = false;
337         } else {
338             Map<String, ?> keyFieldValues = getDataObjectMetaDataService().getPrimaryKeyFieldValues(getDataObject());
339             for (Object keyValue : keyFieldValues.values()) {
340                 if (keyValue == null) {
341                     isOldDataObjectInExistence = false;
342                 } else if ((keyValue instanceof String) && StringUtils.isBlank((String) keyValue)) {
343                     isOldDataObjectInExistence = false;
344                 }
345 
346                 if (!isOldDataObjectInExistence) {
347                     break;
348                 }
349             }
350         }
351 
352         return isOldDataObjectInExistence;
353     }
354 
355     /**
356      * @see org.kuali.rice.krad.maintenance.Maintainable#prepareForSave
357      */
358     @Override
359     public void prepareForSave() {
360         // by default do nothing
361     }
362 
363     /**
364      * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterRetrieve
365      */
366     @Override
367     public void processAfterRetrieve() {
368         // by default do nothing
369     }
370 
371     /**
372      * @see org.kuali.rice.krad.maintenance.MaintainableImpl#setupNewFromExisting
373      */
374     @Override
375     public void setupNewFromExisting(MaintenanceDocument document, Map<String, String[]> parameters) {
376         // by default do nothing
377     }
378 
379     /**
380      * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterCopy
381      */
382     @Override
383     public void processAfterCopy(MaintenanceDocument document, Map<String, String[]> requestParameters) {
384         // by default do nothing
385     }
386 
387     /**
388      * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterEdit
389      */
390     @Override
391     public void processAfterEdit(MaintenanceDocument document, Map<String, String[]> requestParameters) {
392         // by default do nothing
393     }
394 
395     /**
396      * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterNew
397      */
398     @Override
399     public void processAfterNew(MaintenanceDocument document, Map<String, String[]> requestParameters) {
400         // by default do nothing
401     }
402 
403     /**
404      * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterPost
405      */
406     @Override
407     public void processAfterPost(MaintenanceDocument document, Map<String, String[]> requestParameters) {
408         // by default do nothing
409     }
410 
411     /**
412      * In the case of edit maintenance adds a new blank line to the old side
413      *
414      * TODO: should this write some sort of missing message on the old side
415      * instead?
416      *
417      * @see org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl#processAfterAddLine(org.kuali.rice.krad.uif.view.View,
418      *      org.kuali.rice.krad.uif.container.CollectionGroup, java.lang.Object,
419      *      java.lang.Object)
420      */
421     @Override
422     protected void processAfterAddLine(View view, CollectionGroup collectionGroup, Object model, Object addLine) {
423         super.processAfterAddLine(view, collectionGroup, model, addLine);
424         
425         // Check for maintenance documents in edit but exclude notes
426         if (model instanceof MaintenanceForm && KRADConstants.MAINTENANCE_EDIT_ACTION.equals(((MaintenanceForm)model).getMaintenanceAction()) && !(addLine instanceof Note)) {
427             MaintenanceForm maintenanceForm = (MaintenanceForm) model;
428             MaintenanceDocument document = maintenanceForm.getDocument();
429 
430             // get the old object's collection
431             Collection<Object> oldCollection = ObjectPropertyUtils
432                     .getPropertyValue(document.getOldMaintainableObject().getDataObject(),
433                             collectionGroup.getPropertyName());
434             try {
435                 Object blankLine = collectionGroup.getCollectionObjectClass().newInstance();
436                 oldCollection.add(blankLine);
437             } catch (Exception e) {
438                 throw new RuntimeException("Unable to create new line instance for old maintenance object", e);
439             }
440         }
441     }
442     
443     /**
444      * In the case of edit maintenance deleted the item on the old side
445      *
446      *
447      * @see org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl#processAfterDeleteLine(View,
448      *      org.kuali.rice.krad.uif.container.CollectionGroup, java.lang.Object,  int)
449      */
450     @Override
451     protected void processAfterDeleteLine(View view, CollectionGroup collectionGroup, Object model, int lineIndex) {
452         super.processAfterDeleteLine(view, collectionGroup, model, lineIndex);
453         
454         // Check for maintenance documents in edit but exclude notes
455         if (model instanceof MaintenanceForm && KRADConstants.MAINTENANCE_EDIT_ACTION.equals(((MaintenanceForm)model).getMaintenanceAction()) 
456                 && !collectionGroup.getCollectionObjectClass().getName().equals(Note.class.getName())) {
457             MaintenanceForm maintenanceForm = (MaintenanceForm) model;
458             MaintenanceDocument document = maintenanceForm.getDocument();
459 
460             // get the old object's collection
461             Collection<Object> oldCollection = ObjectPropertyUtils
462                     .getPropertyValue(document.getOldMaintainableObject().getDataObject(),
463                             collectionGroup.getPropertyName());
464             try {
465                 // Remove the object at lineIndex from the collection
466                 oldCollection.remove(oldCollection.toArray()[lineIndex]);
467             } catch (Exception e) {
468                 throw new RuntimeException("Unable to delete line instance for old maintenance object", e);
469             }
470         }
471     }    
472 
473     /**
474      * Retrieves the document number configured on this maintainable
475      *
476      * @return String document number
477      */
478     protected String getDocumentNumber() {
479         return this.documentNumber;
480     }
481 
482     protected LookupService getLookupService() {
483         if (lookupService == null) {
484             lookupService = KRADServiceLocatorWeb.getLookupService();
485         }
486         return this.lookupService;
487     }
488 
489     public void setLookupService(LookupService lookupService) {
490         this.lookupService = lookupService;
491     }
492 
493     protected DataObjectAuthorizationService getDataObjectAuthorizationService() {
494         if (dataObjectAuthorizationService == null) {
495             this.dataObjectAuthorizationService = KRADServiceLocatorWeb.getDataObjectAuthorizationService();
496         }
497         return dataObjectAuthorizationService;
498     }
499 
500     public void setDataObjectAuthorizationService(DataObjectAuthorizationService dataObjectAuthorizationService) {
501         this.dataObjectAuthorizationService = dataObjectAuthorizationService;
502     }
503 
504     protected DataObjectMetaDataService getDataObjectMetaDataService() {
505         if (dataObjectMetaDataService == null) {
506             this.dataObjectMetaDataService = KRADServiceLocatorWeb.getDataObjectMetaDataService();
507         }
508         return dataObjectMetaDataService;
509     }
510 
511     public void setDataObjectMetaDataService(DataObjectMetaDataService dataObjectMetaDataService) {
512         this.dataObjectMetaDataService = dataObjectMetaDataService;
513     }
514 
515     public DocumentDictionaryService getDocumentDictionaryService() {
516         if (documentDictionaryService == null) {
517             this.documentDictionaryService = KRADServiceLocatorWeb.getDocumentDictionaryService();
518         }
519         return documentDictionaryService;
520     }
521 
522     public void setDocumentDictionaryService(DocumentDictionaryService documentDictionaryService) {
523         this.documentDictionaryService = documentDictionaryService;
524     }
525 
526     protected EncryptionService getEncryptionService() {
527         if (encryptionService == null) {
528             encryptionService = CoreApiServiceLocator.getEncryptionService();
529         }
530         return encryptionService;
531     }
532 
533     public void setEncryptionService(EncryptionService encryptionService) {
534         this.encryptionService = encryptionService;
535     }
536 
537     protected BusinessObjectService getBusinessObjectService() {
538         if (businessObjectService == null) {
539             businessObjectService = KRADServiceLocator.getBusinessObjectService();
540         }
541         return businessObjectService;
542     }
543 
544     public void setBusinessObjectService(BusinessObjectService businessObjectService) {
545         this.businessObjectService = businessObjectService;
546     }
547 
548     protected MaintenanceDocumentService getMaintenanceDocumentService() {
549         if (maintenanceDocumentService == null) {
550             maintenanceDocumentService = KRADServiceLocatorWeb.getMaintenanceDocumentService();
551         }
552         return maintenanceDocumentService;
553     }
554 
555     public void setMaintenanceDocumentService(MaintenanceDocumentService maintenanceDocumentService) {
556         this.maintenanceDocumentService = maintenanceDocumentService;
557     }
558 }