001/**
002 * Copyright 2005-2016 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 */
016package org.kuali.rice.krad.maintenance;
017
018import org.apache.commons.lang.StringUtils;
019import org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException;
020import org.kuali.rice.core.api.CoreApiServiceLocator;
021import org.kuali.rice.core.api.encryption.EncryptionService;
022import org.kuali.rice.kim.api.identity.Person;
023import org.kuali.rice.krad.bo.BusinessObject;
024import org.kuali.rice.krad.bo.DocumentHeader;
025import org.kuali.rice.krad.bo.Note;
026import org.kuali.rice.krad.bo.PersistableBusinessObject;
027import org.kuali.rice.krad.exception.PessimisticLockingException;
028import org.kuali.rice.krad.service.BusinessObjectService;
029import org.kuali.rice.krad.service.DataObjectAuthorizationService;
030import org.kuali.rice.krad.service.DataObjectMetaDataService;
031import org.kuali.rice.krad.service.DocumentDictionaryService;
032import org.kuali.rice.krad.service.KRADServiceLocator;
033import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
034import org.kuali.rice.krad.service.LookupService;
035import org.kuali.rice.krad.service.MaintenanceDocumentService;
036import org.kuali.rice.krad.uif.component.BindingInfo;
037import org.kuali.rice.krad.uif.container.CollectionGroup;
038import org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl;
039import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
040import org.kuali.rice.krad.uif.view.View;
041import org.kuali.rice.krad.util.KRADConstants;
042import org.kuali.rice.krad.util.ObjectUtils;
043import org.kuali.rice.krad.web.form.MaintenanceForm;
044
045import java.security.GeneralSecurityException;
046import java.util.ArrayList;
047import java.util.Collection;
048import java.util.Iterator;
049import java.util.List;
050import java.util.Map;
051
052/**
053 * Default implementation of the <code>Maintainable</code> interface
054 *
055 * @author Kuali Rice Team (rice.collab@kuali.org)
056 */
057public class MaintainableImpl extends ViewHelperServiceImpl implements Maintainable {
058    private static final long serialVersionUID = 9125271369161634992L;
059
060    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(MaintainableImpl.class);
061
062    private String documentNumber;
063    private Object dataObject;
064    private Class<?> dataObjectClass;
065    private String maintenanceAction;
066
067    private transient LookupService lookupService;
068    private transient DataObjectAuthorizationService dataObjectAuthorizationService;
069    private transient DataObjectMetaDataService dataObjectMetaDataService;
070    private transient DocumentDictionaryService documentDictionaryService;
071    private transient EncryptionService encryptionService;
072    private transient BusinessObjectService businessObjectService;
073    private transient MaintenanceDocumentService maintenanceDocumentService;
074
075    /**
076     * @see org.kuali.rice.krad.maintenance.Maintainable#retrieveObjectForEditOrCopy(MaintenanceDocument, java.util.Map)
077     */
078    @Override
079    public Object retrieveObjectForEditOrCopy(MaintenanceDocument document, Map<String, String> dataObjectKeys) {
080        Object dataObject = null;
081
082        try {
083            dataObject = getLookupService().findObjectBySearch(getDataObjectClass(), dataObjectKeys);
084        } catch (ClassNotPersistenceCapableException ex) {
085            if (!document.getOldMaintainableObject().isExternalBusinessObject()) {
086                throw new RuntimeException("Data Object Class: "
087                        + getDataObjectClass()
088                        + " is not persistable and is not externalizable - configuration error");
089            }
090            // otherwise, let fall through
091        }
092
093        return dataObject;
094    }
095
096    /**
097     * @see org.kuali.rice.krad.maintenance.Maintainable#setDocumentNumber
098     */
099    @Override
100    public void setDocumentNumber(String documentNumber) {
101        this.documentNumber = documentNumber;
102    }
103
104    /**
105     * @see org.kuali.rice.krad.maintenance.Maintainable#getDocumentTitle
106     */
107    @Override
108    public String getDocumentTitle(MaintenanceDocument document) {
109        // default implementation is to allow MaintenanceDocumentBase to
110        // generate the doc title
111        return "";
112    }
113
114    /**
115     * @see org.kuali.rice.krad.maintenance.Maintainable#getDataObject
116     */
117    @Override
118    public Object getDataObject() {
119        return dataObject;
120    }
121
122    /**
123     * @see org.kuali.rice.krad.maintenance.Maintainable#setDataObject
124     */
125    @Override
126    public void setDataObject(Object object) {
127        this.dataObject = object;
128    }
129
130    /**
131     * @see org.kuali.rice.krad.maintenance.Maintainable#getDataObjectClass
132     */
133    @Override
134    public Class getDataObjectClass() {
135        return dataObjectClass;
136    }
137
138    /**
139     * @see org.kuali.rice.krad.maintenance.Maintainable#setDataObjectClass
140     */
141    @Override
142    public void setDataObjectClass(Class dataObjectClass) {
143        this.dataObjectClass = dataObjectClass;
144    }
145
146    /**
147     * Persistable business objects are lockable
148     *
149     * @see org.kuali.rice.krad.maintenance.Maintainable#isLockable
150     */
151    @Override
152    public boolean isLockable() {
153        return KRADServiceLocator.getPersistenceStructureService().isPersistable(getDataObject().getClass());
154    }
155
156    /**
157     * Returns the data object if its persistable, null otherwise
158     *
159     * @see org.kuali.rice.krad.maintenance.Maintainable#getPersistableBusinessObject
160     */
161    @Override
162    public PersistableBusinessObject getPersistableBusinessObject() {
163        if (KRADServiceLocator.getPersistenceStructureService().isPersistable(getDataObject().getClass())) {
164            return (PersistableBusinessObject) getDataObject();
165        } else {
166            return null;
167        }
168
169    }
170
171    /**
172     * @see org.kuali.rice.krad.maintenance.Maintainable#getMaintenanceAction
173     */
174    @Override
175    public String getMaintenanceAction() {
176        return maintenanceAction;
177    }
178
179    /**
180     * @see org.kuali.rice.krad.maintenance.Maintainable#setMaintenanceAction
181     */
182    @Override
183    public void setMaintenanceAction(String maintenanceAction) {
184        this.maintenanceAction = maintenanceAction;
185    }
186
187    /**
188     * Note: as currently implemented, every key field for a given
189     * data object class must have a visible getter
190     *
191     * @see org.kuali.rice.krad.maintenance.Maintainable#generateMaintenanceLocks
192     */
193    @Override
194    public List<MaintenanceLock> generateMaintenanceLocks() {
195        List<MaintenanceLock> maintenanceLocks = new ArrayList<MaintenanceLock>();
196        StringBuffer lockRepresentation = new StringBuffer(dataObjectClass.getName());
197        lockRepresentation.append(KRADConstants.Maintenance.LOCK_AFTER_CLASS_DELIM);
198
199        Object bo = getDataObject();
200        List keyFieldNames = getDocumentDictionaryService().getLockingKeys(getDocumentTypeName());
201
202        for (Iterator i = keyFieldNames.iterator(); i.hasNext(); ) {
203            String fieldName = (String) i.next();
204            Object fieldValue = ObjectUtils.getPropertyValue(bo, fieldName);
205            if (fieldValue == null) {
206                fieldValue = "";
207            }
208
209            // check if field is a secure
210            if (getDataObjectAuthorizationService()
211                    .attributeValueNeedsToBeEncryptedOnFormsAndLinks(dataObjectClass, fieldName)) {
212                try {
213                    if(CoreApiServiceLocator.getEncryptionService().isEnabled()) {
214                        fieldValue = getEncryptionService().encrypt(fieldValue);
215                    }
216                } catch (GeneralSecurityException e) {
217                    LOG.error("Unable to encrypt secure field for locking representation " + e.getMessage());
218                    throw new RuntimeException(
219                            "Unable to encrypt secure field for locking representation " + e.getMessage());
220                }
221            }
222
223            lockRepresentation.append(fieldName);
224            lockRepresentation.append(KRADConstants.Maintenance.LOCK_AFTER_FIELDNAME_DELIM);
225            lockRepresentation.append(String.valueOf(fieldValue));
226            if (i.hasNext()) {
227                lockRepresentation.append(KRADConstants.Maintenance.LOCK_AFTER_VALUE_DELIM);
228            }
229        }
230
231        MaintenanceLock maintenanceLock = new MaintenanceLock();
232        maintenanceLock.setDocumentNumber(documentNumber);
233        maintenanceLock.setLockingRepresentation(lockRepresentation.toString());
234        maintenanceLocks.add(maintenanceLock);
235
236        return maintenanceLocks;
237    }
238
239    /**
240     * Retrieves the document type name from the data dictionary based on
241     * business object class
242     */
243    protected String getDocumentTypeName() {
244        return getDocumentDictionaryService().getMaintenanceDocumentTypeName(dataObjectClass);
245    }
246
247    /**
248     * @see org.kuali.rice.krad.maintenance.Maintainable#saveDataObject
249     */
250    @Override
251    public void saveDataObject() {
252        if (dataObject instanceof PersistableBusinessObject) {
253            getBusinessObjectService().linkAndSave((PersistableBusinessObject) dataObject);
254        } else {
255            throw new RuntimeException(
256                    "Cannot save object of type: " + dataObjectClass + " with business object service");
257        }
258    }
259
260    /**
261     * @see org.kuali.rice.krad.maintenance.Maintainable#deleteDataObject
262     */
263    @Override
264    public void deleteDataObject() {
265        if (dataObject == null) {
266            return;
267        }
268
269        if (dataObject instanceof PersistableBusinessObject) {
270            getBusinessObjectService().delete((PersistableBusinessObject) dataObject);
271            dataObject = null;
272        } else {
273            throw new RuntimeException(
274                    "Cannot delete object of type: " + dataObjectClass + " with business object service");
275        }
276    }
277
278    /**
279     * @see org.kuali.rice.krad.maintenance.Maintainable#doRouteStatusChange
280     */
281    @Override
282    public void doRouteStatusChange(DocumentHeader documentHeader) {
283        // no default implementation
284    }
285
286    /**
287     * @see org.kuali.rice.krad.maintenance.Maintainable#getLockingDocumentId
288     */
289    @Override
290    public String getLockingDocumentId() {
291        return getMaintenanceDocumentService().getLockingDocumentId(this, documentNumber);
292    }
293
294    /**
295     * @see org.kuali.rice.krad.maintenance.Maintainable#getWorkflowEngineDocumentIdsToLock
296     */
297    @Override
298    public List<String> getWorkflowEngineDocumentIdsToLock() {
299        return null;
300    }
301
302    /**
303     * Default implementation simply returns false to indicate that custom
304     * lock descriptors are not supported by MaintainableImpl. If custom
305     * lock descriptors are needed, the appropriate subclasses should override
306     * this method
307     *
308     * @see org.kuali.rice.krad.maintenance.Maintainable#useCustomLockDescriptors
309     */
310    @Override
311    public boolean useCustomLockDescriptors() {
312        return false;
313    }
314
315    /**
316     * Default implementation just throws a PessimisticLockingException.
317     * Subclasses of MaintainableImpl that need support for custom lock
318     * descriptors should override this method
319     *
320     * @see org.kuali.rice.krad.maintenance.Maintainable#getCustomLockDescriptor
321     */
322    @Override
323    public String getCustomLockDescriptor(Person user) {
324        throw new PessimisticLockingException("The Maintainable for document " + documentNumber +
325                " is using pessimistic locking with custom lock descriptors, but the Maintainable has not overridden the getCustomLockDescriptor method");
326    }
327
328    /**
329     * @see org.kuali.rice.krad.maintenance.Maintainable#isNotesEnabled
330     */
331    @Override
332    public boolean isNotesEnabled() {
333        return getDataObjectMetaDataService().areNotesSupported(dataObjectClass);
334    }
335
336    /**
337     * @see org.kuali.rice.krad.maintenance.MaintainableImpl#isExternalBusinessObject
338     */
339    @Override
340    public boolean isExternalBusinessObject() {
341        return false;
342    }
343
344    /**
345     * @see org.kuali.rice.krad.maintenance.MaintainableImpl#prepareExternalBusinessObject
346     */
347    @Override
348    public void prepareExternalBusinessObject(BusinessObject businessObject) {
349        // by default do nothing
350    }
351
352    /**
353     * Checks whether the data object is not null and has its primary key values populated
354     *
355     * @see org.kuali.rice.krad.maintenance.MaintainableImpl#isOldDataObjectInDocument
356     */
357    @Override
358    public boolean isOldDataObjectInDocument() {
359        boolean isOldDataObjectInExistence = true;
360
361        if (getDataObject() == null) {
362            isOldDataObjectInExistence = false;
363        } else {
364            Map<String, ?> keyFieldValues = getDataObjectMetaDataService().getPrimaryKeyFieldValues(getDataObject());
365            for (Object keyValue : keyFieldValues.values()) {
366                if (keyValue == null) {
367                    isOldDataObjectInExistence = false;
368                } else if ((keyValue instanceof String) && StringUtils.isBlank((String) keyValue)) {
369                    isOldDataObjectInExistence = false;
370                }
371
372                if (!isOldDataObjectInExistence) {
373                    break;
374                }
375            }
376        }
377
378        return isOldDataObjectInExistence;
379    }
380
381    /**
382     * @see org.kuali.rice.krad.maintenance.Maintainable#prepareForSave
383     */
384    @Override
385    public void prepareForSave() {
386        // by default do nothing
387    }
388
389    /**
390     * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterRetrieve
391     */
392    @Override
393    public void processAfterRetrieve() {
394        // by default do nothing
395    }
396
397    /**
398     * @see org.kuali.rice.krad.maintenance.MaintainableImpl#setupNewFromExisting
399     */
400    @Override
401    public void setupNewFromExisting(MaintenanceDocument document, Map<String, String[]> parameters) {
402        // by default do nothing
403    }
404
405    /**
406     * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterCopy
407     */
408    @Override
409    public void processAfterCopy(MaintenanceDocument document, Map<String, String[]> requestParameters) {
410        // by default do nothing
411    }
412
413    /**
414     * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterEdit
415     */
416    @Override
417    public void processAfterEdit(MaintenanceDocument document, Map<String, String[]> requestParameters) {
418        // by default do nothing
419    }
420
421    /**
422     * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterNew
423     */
424    @Override
425    public void processAfterNew(MaintenanceDocument document, Map<String, String[]> requestParameters) {
426        // by default do nothing
427    }
428
429    /**
430     * @see org.kuali.rice.krad.maintenance.Maintainable#processAfterPost
431     */
432    @Override
433    public void processAfterPost(MaintenanceDocument document, Map<String, String[]> requestParameters) {
434        // by default do nothing
435    }
436
437    /**
438     * In the case of edit maintenance adds a new blank line to the old side
439     *
440     * TODO: should this write some sort of missing message on the old side
441     * instead?
442     *
443     * @see org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl#processAfterAddLine(org.kuali.rice.krad.uif.view.View,
444     *      org.kuali.rice.krad.uif.container.CollectionGroup, java.lang.Object,
445     *      java.lang.Object)
446     */
447    @Override
448    protected void processAfterAddLine(View view, CollectionGroup collectionGroup, Object model, Object addLine) {
449        super.processAfterAddLine(view, collectionGroup, model, addLine);
450        
451        // Check for maintenance documents in edit but exclude notes
452        if (model instanceof MaintenanceForm && KRADConstants.MAINTENANCE_EDIT_ACTION.equals(((MaintenanceForm)model).getMaintenanceAction()) && !(addLine instanceof Note)) {
453            MaintenanceForm maintenanceForm = (MaintenanceForm) 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            try {
469                Object blankLine = collectionGroup.getCollectionObjectClass().newInstance();
470                oldCollection.add(blankLine);
471            } catch (Exception e) {
472                throw new RuntimeException("Unable to create new line instance for old maintenance object", e);
473            }
474        }
475    }
476    
477    /**
478     * In the case of edit maintenance deleted the item on the old side
479     *
480     *
481     * @see org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl#processAfterDeleteLine(View,
482     *      org.kuali.rice.krad.uif.container.CollectionGroup, java.lang.Object,  int)
483     */
484    @Override
485    protected void processAfterDeleteLine(View view, CollectionGroup collectionGroup, Object model, int lineIndex) {
486        super.processAfterDeleteLine(view, collectionGroup, model, lineIndex);
487        
488        // Check for maintenance documents in edit but exclude notes
489        if (model instanceof MaintenanceForm && KRADConstants.MAINTENANCE_EDIT_ACTION.equals(((MaintenanceForm)model).getMaintenanceAction()) 
490                && !collectionGroup.getCollectionObjectClass().getName().equals(Note.class.getName())) {
491            MaintenanceForm maintenanceForm = (MaintenanceForm) model;
492            MaintenanceDocument document = maintenanceForm.getDocument();
493
494            // get the old object's collection
495            Collection<Object> oldCollection = ObjectPropertyUtils
496                    .getPropertyValue(document.getOldMaintainableObject().getDataObject(),
497                            collectionGroup.getPropertyName());
498            try {
499                // Remove the object at lineIndex from the collection
500                oldCollection.remove(oldCollection.toArray()[lineIndex]);
501            } catch (Exception e) {
502                throw new RuntimeException("Unable to delete line instance for old maintenance object", e);
503            }
504        }
505    }    
506
507    /**
508     * Retrieves the document number configured on this maintainable
509     *
510     * @return String document number
511     */
512    protected String getDocumentNumber() {
513        return this.documentNumber;
514    }
515
516    protected LookupService getLookupService() {
517        if (lookupService == null) {
518            lookupService = KRADServiceLocatorWeb.getLookupService();
519        }
520        return this.lookupService;
521    }
522
523    public void setLookupService(LookupService lookupService) {
524        this.lookupService = lookupService;
525    }
526
527    protected DataObjectAuthorizationService getDataObjectAuthorizationService() {
528        if (dataObjectAuthorizationService == null) {
529            this.dataObjectAuthorizationService = KRADServiceLocatorWeb.getDataObjectAuthorizationService();
530        }
531        return dataObjectAuthorizationService;
532    }
533
534    public void setDataObjectAuthorizationService(DataObjectAuthorizationService dataObjectAuthorizationService) {
535        this.dataObjectAuthorizationService = dataObjectAuthorizationService;
536    }
537
538    protected DataObjectMetaDataService getDataObjectMetaDataService() {
539        if (dataObjectMetaDataService == null) {
540            this.dataObjectMetaDataService = KRADServiceLocatorWeb.getDataObjectMetaDataService();
541        }
542        return dataObjectMetaDataService;
543    }
544
545    public void setDataObjectMetaDataService(DataObjectMetaDataService dataObjectMetaDataService) {
546        this.dataObjectMetaDataService = dataObjectMetaDataService;
547    }
548
549    public DocumentDictionaryService getDocumentDictionaryService() {
550        if (documentDictionaryService == null) {
551            this.documentDictionaryService = KRADServiceLocatorWeb.getDocumentDictionaryService();
552        }
553        return documentDictionaryService;
554    }
555
556    public void setDocumentDictionaryService(DocumentDictionaryService documentDictionaryService) {
557        this.documentDictionaryService = documentDictionaryService;
558    }
559
560    protected EncryptionService getEncryptionService() {
561        if (encryptionService == null) {
562            encryptionService = CoreApiServiceLocator.getEncryptionService();
563        }
564        return encryptionService;
565    }
566
567    public void setEncryptionService(EncryptionService encryptionService) {
568        this.encryptionService = encryptionService;
569    }
570
571    protected BusinessObjectService getBusinessObjectService() {
572        if (businessObjectService == null) {
573            businessObjectService = KRADServiceLocator.getBusinessObjectService();
574        }
575        return businessObjectService;
576    }
577
578    public void setBusinessObjectService(BusinessObjectService businessObjectService) {
579        this.businessObjectService = businessObjectService;
580    }
581
582    protected MaintenanceDocumentService getMaintenanceDocumentService() {
583        if (maintenanceDocumentService == null) {
584            maintenanceDocumentService = KRADServiceLocatorWeb.getMaintenanceDocumentService();
585        }
586        return maintenanceDocumentService;
587    }
588
589    public void setMaintenanceDocumentService(MaintenanceDocumentService maintenanceDocumentService) {
590        this.maintenanceDocumentService = maintenanceDocumentService;
591    }
592}