001    /**
002     * Copyright 2005-2011 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.krad.maintenance;
017    
018    import org.apache.commons.lang.StringUtils;
019    import org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException;
020    import org.kuali.rice.core.api.CoreApiServiceLocator;
021    import org.kuali.rice.core.api.encryption.EncryptionService;
022    import org.kuali.rice.kim.api.identity.Person;
023    import org.kuali.rice.krad.bo.BusinessObject;
024    import org.kuali.rice.krad.bo.DocumentHeader;
025    import org.kuali.rice.krad.bo.Note;
026    import org.kuali.rice.krad.bo.PersistableBusinessObject;
027    import org.kuali.rice.krad.document.MaintenanceDocument;
028    import org.kuali.rice.krad.document.MaintenanceLock;
029    import org.kuali.rice.krad.exception.PessimisticLockingException;
030    import org.kuali.rice.krad.service.BusinessObjectService;
031    import org.kuali.rice.krad.service.DataObjectAuthorizationService;
032    import org.kuali.rice.krad.service.DataObjectMetaDataService;
033    import org.kuali.rice.krad.service.DocumentDictionaryService;
034    import org.kuali.rice.krad.service.KRADServiceLocator;
035    import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
036    import org.kuali.rice.krad.service.LookupService;
037    import org.kuali.rice.krad.service.MaintenanceDocumentService;
038    import org.kuali.rice.krad.uif.container.CollectionGroup;
039    import org.kuali.rice.krad.uif.view.View;
040    import org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl;
041    import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
042    import org.kuali.rice.krad.util.KRADConstants;
043    import org.kuali.rice.krad.util.ObjectUtils;
044    import org.kuali.rice.krad.web.form.MaintenanceForm;
045    
046    import java.security.GeneralSecurityException;
047    import java.util.ArrayList;
048    import java.util.Collection;
049    import java.util.Iterator;
050    import java.util.List;
051    import java.util.Map;
052    
053    /**
054     * Default implementation of the <code>Maintainable</code> interface
055     *
056     * @author Kuali Rice Team (rice.collab@kuali.org)
057     */
058    public class MaintainableImpl extends ViewHelperServiceImpl implements Maintainable {
059        private static final long serialVersionUID = 9125271369161634992L;
060    
061        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(MaintainableImpl.class);
062    
063        private String documentNumber;
064        private Object dataObject;
065        private Class<?> dataObjectClass;
066        private String maintenanceAction;
067    
068        private transient LookupService lookupService;
069        private transient DataObjectAuthorizationService dataObjectAuthorizationService;
070        private transient DataObjectMetaDataService dataObjectMetaDataService;
071        private transient DocumentDictionaryService documentDictionaryService;
072        private transient EncryptionService encryptionService;
073        private transient BusinessObjectService businessObjectService;
074        private transient MaintenanceDocumentService maintenanceDocumentService;
075    
076        /**
077         * @see org.kuali.rice.krad.maintenance.Maintainable#retrieveObjectForEditOrCopy(MaintenanceDocument, java.util.Map)
078         */
079        @Override
080        public Object retrieveObjectForEditOrCopy(MaintenanceDocument document, Map<String, String> dataObjectKeys) {
081            Object dataObject = null;
082    
083            try {
084                dataObject = getLookupService().findObjectBySearch(getDataObjectClass(), dataObjectKeys);
085            } catch (ClassNotPersistenceCapableException ex) {
086                if (!document.getOldMaintainableObject().isExternalBusinessObject()) {
087                    throw new RuntimeException("Data Object Class: "
088                            + getDataObjectClass()
089                            + " is not persistable and is not externalizable - configuration error");
090                }
091                // otherwise, let fall through
092            }
093    
094            return dataObject;
095        }
096    
097        /**
098         * @see org.kuali.rice.krad.maintenance.Maintainable#setDocumentNumber
099         */
100        @Override
101        public void setDocumentNumber(String documentNumber) {
102            this.documentNumber = documentNumber;
103        }
104    
105        /**
106         * @see org.kuali.rice.krad.maintenance.Maintainable#getDocumentTitle
107         */
108        @Override
109        public String getDocumentTitle(MaintenanceDocument document) {
110            // default implementation is to allow MaintenanceDocumentBase to
111            // generate the doc title
112            return "";
113        }
114    
115        /**
116         * @see org.kuali.rice.krad.maintenance.Maintainable#getDataObject
117         */
118        @Override
119        public Object getDataObject() {
120            return dataObject;
121        }
122    
123        /**
124         * @see org.kuali.rice.krad.maintenance.Maintainable#setDataObject
125         */
126        @Override
127        public void setDataObject(Object object) {
128            this.dataObject = object;
129        }
130    
131        /**
132         * @see org.kuali.rice.krad.maintenance.Maintainable#getDataObjectClass
133         */
134        @Override
135        public Class getDataObjectClass() {
136            return dataObjectClass;
137        }
138    
139        /**
140         * @see org.kuali.rice.krad.maintenance.Maintainable#setDataObjectClass
141         */
142        @Override
143        public void setDataObjectClass(Class dataObjectClass) {
144            this.dataObjectClass = dataObjectClass;
145        }
146    
147        /**
148         * @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    }