View Javadoc

1   /**
2    * Copyright 2005-2012 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.service.impl;
17  
18  import java.io.Serializable;
19  import java.util.Arrays;
20  import java.util.List;
21  import java.util.Map;
22  
23  import org.apache.commons.lang.StringUtils;
24  import org.apache.log4j.Logger;
25  import org.kuali.rice.core.framework.persistence.jta.TransactionalNoValidationExceptionRollback;
26  import org.kuali.rice.kew.api.exception.WorkflowException;
27  import org.kuali.rice.kim.api.identity.Person;
28  import org.kuali.rice.krad.bo.BusinessObject;
29  import org.kuali.rice.krad.bo.PersistableBusinessObject;
30  import org.kuali.rice.krad.dao.MaintenanceDocumentDao;
31  import org.kuali.rice.krad.maintenance.MaintenanceDocument;
32  import org.kuali.rice.krad.maintenance.MaintenanceLock;
33  import org.kuali.rice.krad.exception.DocumentTypeAuthorizationException;
34  import org.kuali.rice.krad.maintenance.Maintainable;
35  import org.kuali.rice.krad.service.DataObjectAuthorizationService;
36  import org.kuali.rice.krad.service.DataObjectMetaDataService;
37  import org.kuali.rice.krad.service.DocumentDictionaryService;
38  import org.kuali.rice.krad.service.DocumentService;
39  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
40  import org.kuali.rice.krad.service.MaintenanceDocumentService;
41  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
42  import org.kuali.rice.krad.util.GlobalVariables;
43  import org.kuali.rice.krad.util.KRADConstants;
44  import org.kuali.rice.krad.util.KRADUtils;
45  import org.kuali.rice.krad.util.ObjectUtils;
46  
47  /**
48   * Service implementation for the MaintenanceDocument structure. This is the
49   * default implementation, that is delivered with Kuali
50   *
51   * @author Kuali Rice Team (rice.collab@kuali.org)
52   */
53  @TransactionalNoValidationExceptionRollback
54  public class MaintenanceDocumentServiceImpl implements MaintenanceDocumentService {
55      protected static final Logger LOG = Logger.getLogger(MaintenanceDocumentServiceImpl.class);
56  
57      private MaintenanceDocumentDao maintenanceDocumentDao;
58      private DataObjectAuthorizationService dataObjectAuthorizationService;
59      private DocumentService documentService;
60      private DataObjectMetaDataService dataObjectMetaDataService;
61      private DocumentDictionaryService documentDictionaryService;
62  
63      /**
64       * @see org.kuali.rice.krad.service.MaintenanceDocumentService#setupNewMaintenanceDocument(java.lang.String,
65       *      java.lang.String, java.lang.String)
66       */
67      @SuppressWarnings("unchecked")
68      public MaintenanceDocument setupNewMaintenanceDocument(String objectClassName, String documentTypeName,
69              String maintenanceAction) {
70          if (StringUtils.isEmpty(objectClassName) && StringUtils.isEmpty(documentTypeName)) {
71              throw new IllegalArgumentException("Document type name or bo class not given!");
72          }
73  
74          // get document type if not passed
75          if (StringUtils.isEmpty(documentTypeName)) {
76              try {
77                  documentTypeName =
78                          getDocumentDictionaryService().getMaintenanceDocumentTypeName(Class.forName(objectClassName));
79              } catch (ClassNotFoundException e) {
80                  throw new RuntimeException(e);
81              }
82  
83              if (StringUtils.isEmpty(documentTypeName)) {
84                  throw new RuntimeException(
85                          "documentTypeName is empty; does this Business Object have a maintenance document definition? " +
86                                  objectClassName);
87              }
88          }
89  
90          // check doc type allows new or copy if that action was requested
91          if (KRADConstants.MAINTENANCE_NEW_ACTION.equals(maintenanceAction) ||
92                  KRADConstants.MAINTENANCE_COPY_ACTION.equals(maintenanceAction)) {
93              Class<?> boClass =
94                      getDocumentDictionaryService().getMaintenanceDataObjectClass(documentTypeName);
95              boolean allowsNewOrCopy = getDataObjectAuthorizationService()
96                      .canCreate(boClass, GlobalVariables.getUserSession().getPerson(), documentTypeName);
97              if (!allowsNewOrCopy) {
98                  LOG.error("Document type " + documentTypeName + " does not allow new or copy actions.");
99                  throw new DocumentTypeAuthorizationException(
100                         GlobalVariables.getUserSession().getPerson().getPrincipalId(), "newOrCopy", documentTypeName);
101             }
102         }
103 
104         // get new document from service
105         try {
106             return (MaintenanceDocument) getDocumentService().getNewDocument(documentTypeName);
107         } catch (WorkflowException e) {
108             LOG.error("Cannot get new maintenance document instance for doc type: " + documentTypeName, e);
109             throw new RuntimeException("Cannot get new maintenance document instance for doc type: " + documentTypeName,
110                     e);
111         }
112     }
113 
114     /**
115      * @see org.kuali.rice.krad.service.impl.MaintenanceDocumentServiceImpl#setupMaintenanceObject
116      */
117     @Override
118     public void setupMaintenanceObject(MaintenanceDocument document, String maintenanceAction,
119             Map<String, String[]> requestParameters) {
120         document.getNewMaintainableObject().setMaintenanceAction(maintenanceAction);
121         document.getOldMaintainableObject().setMaintenanceAction(maintenanceAction);
122 
123         // if action is edit or copy first need to retrieve the old record
124         if (!KRADConstants.MAINTENANCE_NEW_ACTION.equals(maintenanceAction) &&
125                 !KRADConstants.MAINTENANCE_NEWWITHEXISTING_ACTION.equals(maintenanceAction)) {
126             Object oldDataObject = retrieveObjectForMaintenance(document, requestParameters);
127 
128             // TODO should we be using ObjectUtils? also, this needs dictionary
129             // enhancement to indicate fields to/not to copy
130             Object newDataObject = ObjectUtils.deepCopy((Serializable) oldDataObject);
131 
132             // process further object preparations for copy action
133             if (KRADConstants.MAINTENANCE_COPY_ACTION.equals(maintenanceAction)) {
134                 processMaintenanceObjectForCopy(document, newDataObject, requestParameters);
135             } else {
136                 checkMaintenanceActionAuthorization(document, oldDataObject, maintenanceAction, requestParameters);
137             }
138 
139             // set object instance for editing
140             document.getOldMaintainableObject().setDataObject(oldDataObject);
141             document.getNewMaintainableObject().setDataObject(newDataObject);
142         }
143 
144         // if new with existing we need to populate with passed in parameters
145         if (KRADConstants.MAINTENANCE_NEWWITHEXISTING_ACTION.equals(maintenanceAction)) {
146             Object newBO = document.getNewMaintainableObject().getDataObject();
147             Map<String, String> parameters =
148                     buildKeyMapFromRequest(requestParameters, document.getNewMaintainableObject().getDataObjectClass());
149             ObjectPropertyUtils.copyPropertiesToObject(parameters, newBO);
150             if (newBO instanceof PersistableBusinessObject) {
151                 ((PersistableBusinessObject) newBO).refresh();
152             }
153 
154             document.getNewMaintainableObject().setupNewFromExisting(document, requestParameters);
155         } else if (KRADConstants.MAINTENANCE_NEW_ACTION.equals(maintenanceAction)) {
156             document.getNewMaintainableObject().processAfterNew(document, requestParameters);
157         }
158     }
159 
160     /**
161      * For the edit and delete maintenance actions checks with the
162      * <code>BusinessObjectAuthorizationService</code> to check whether the
163      * action is allowed for the record data. In action is allowed invokes the
164      * custom processing hook on the <code>Maintainble</code>.
165      *
166      * @param document - document instance for the maintenance object
167      * @param oldBusinessObject - the old maintenance record
168      * @param maintenanceAction - type of maintenance action requested
169      * @param requestParameters - map of parameters from the request
170      */
171     protected void checkMaintenanceActionAuthorization(MaintenanceDocument document, Object oldBusinessObject,
172             String maintenanceAction, Map<String, String[]> requestParameters) {
173         if (KRADConstants.MAINTENANCE_EDIT_ACTION.equals(maintenanceAction)) {
174             boolean allowsEdit = getDataObjectAuthorizationService()
175                     .canMaintain(oldBusinessObject, GlobalVariables.getUserSession().getPerson(),
176                             document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName());
177             if (!allowsEdit) {
178                 LOG.error("Document type " + document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName() +
179                         " does not allow edit actions.");
180                 throw new DocumentTypeAuthorizationException(
181                         GlobalVariables.getUserSession().getPerson().getPrincipalId(), "edit",
182                         document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName());
183             }
184 
185             // invoke custom processing method
186             document.getNewMaintainableObject().processAfterEdit(document, requestParameters);
187         }
188         // 3070
189         else if (KRADConstants.MAINTENANCE_DELETE_ACTION.equals(maintenanceAction)) {
190             boolean allowsDelete = getDataObjectAuthorizationService()
191                     .canMaintain((BusinessObject) oldBusinessObject, GlobalVariables.getUserSession().getPerson(),
192                             document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName());
193             if (!allowsDelete) {
194                 LOG.error("Document type " + document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName() +
195                         " does not allow delete actions.");
196                 throw new DocumentTypeAuthorizationException(
197                         GlobalVariables.getUserSession().getPerson().getPrincipalId(), "delete",
198                         document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName());
199             }
200         }
201     }
202 
203     /**
204      * For the edit or copy actions retrieves the record that is to be
205      * maintained
206      *
207      * <p>
208      * Based on the persistence metadata for the maintenance object class
209      * retrieves the primary key values from the given request parameters map
210      * (if the class is persistable). With those key values attempts to find the
211      * record using the <code>LookupService</code>.
212      * </p>
213      *
214      * @param document - document instance for the maintenance object
215      * @param requestParameters - Map of parameters from the request
216      * @return Object the retrieved old object
217      */
218     protected Object retrieveObjectForMaintenance(MaintenanceDocument document,
219             Map<String, String[]> requestParameters) {
220         Map<String, String> keyMap =
221                 buildKeyMapFromRequest(requestParameters, document.getNewMaintainableObject().getDataObjectClass());
222 
223         Object oldDataObject = document.getNewMaintainableObject().retrieveObjectForEditOrCopy(document, keyMap);
224 
225         if (oldDataObject == null && !document.getOldMaintainableObject().isExternalBusinessObject()) {
226             throw new RuntimeException(
227                     "Cannot retrieve old record for maintenance document, incorrect parameters passed on maint url: " +
228                             requestParameters);
229         }
230 
231         if (document.getOldMaintainableObject().isExternalBusinessObject()) {
232             if (oldDataObject == null) {
233                 try {
234                     oldDataObject = document.getOldMaintainableObject().getDataObjectClass().newInstance();
235                 } catch (Exception ex) {
236                     throw new RuntimeException(
237                             "External BO maintainable was null and unable to instantiate for old maintainable object.",
238                             ex);
239                 }
240             }
241 
242             populateMaintenanceObjectWithCopyKeyValues(KRADUtils.translateRequestParameterMap(requestParameters),
243                     oldDataObject, document.getOldMaintainableObject());
244             document.getOldMaintainableObject().prepareExternalBusinessObject((PersistableBusinessObject) oldDataObject);
245             oldDataObject = document.getOldMaintainableObject().getDataObject();
246         }
247 
248         return oldDataObject;
249     }
250 
251     /**
252      * For the copy action clears out primary key values for the old record and
253      * does authorization checks on the remaining fields. Also invokes the
254      * custom processing method on the <code>Maintainble</code>
255      *
256      * @param document - document instance for the maintenance object
257      * @param maintenanceObject - the object instance being maintained
258      * @param requestParameters - map of parameters from the request
259      */
260     protected void processMaintenanceObjectForCopy(MaintenanceDocument document, Object maintenanceObject,
261             Map<String, String[]> requestParameters) {
262         if (!document.isFieldsClearedOnCopy()) {
263             Maintainable maintainable = document.getNewMaintainableObject();
264             if (!getDocumentDictionaryService().getPreserveLockingKeysOnCopy(maintainable.getDataObjectClass())) {
265                 clearPrimaryKeyFields(maintenanceObject, maintainable.getDataObjectClass());
266             }
267 
268             clearUnauthorizedNewFields(document);
269 
270             maintainable.processAfterCopy(document, requestParameters);
271 
272             // mark so that this clearing does not happen again
273             document.setFieldsClearedOnCopy(true);
274         }
275     }
276 
277     /**
278      * Clears the value of the primary key fields on the maintenance object
279      *
280      * @param document - document to clear the pk fields on
281      * @param dataObjectClass - class to use for retrieving primary key metadata
282      */
283     protected void clearPrimaryKeyFields(Object maintenanceObject, Class<?> dataObjectClass) {
284         List<String> keyFieldNames = getDataObjectMetaDataService().listPrimaryKeyFieldNames(dataObjectClass);
285         for (String keyFieldName : keyFieldNames) {
286             ObjectPropertyUtils.setPropertyValue(maintenanceObject, keyFieldName, null);
287         }
288     }
289 
290     /**
291      * Used as part of the Copy functionality, to clear any field values that
292      * the user making the copy does not have permissions to modify. This will
293      * prevent authorization errors on a copy.
294      *
295      * @param document - document to be adjusted
296      */
297     protected void clearUnauthorizedNewFields(MaintenanceDocument document) {
298         // get a reference to the current user
299         Person user = GlobalVariables.getUserSession().getPerson();
300 
301         // get a new instance of MaintenanceDocumentAuthorizations for context
302         // TODO: rework for KRAD
303 //        MaintenanceDocumentRestrictions maintenanceDocumentRestrictions =
304 //                getBusinessObjectAuthorizationService().getMaintenanceDocumentRestrictions(document, user);
305 //
306 //        clearBusinessObjectOfRestrictedValues(maintenanceDocumentRestrictions);
307     }
308 
309     /**
310      * Based on the maintenance object class retrieves the key field names from
311      * the <code>BusinessObjectMetaDataService</code> (or alternatively from the
312      * request parameters), then retrieves any matching key value pairs from the
313      * request parameters
314      *
315      * @param requestParameters - map of parameters from the request
316      * @param dataObjectClass - class to use for checking security parameter restrictions
317      * @return Map<String, String> key value pairs
318      */
319     protected Map<String, String> buildKeyMapFromRequest(Map<String, String[]> requestParameters,
320             Class<?> dataObjectClass) {
321         List<String> keyFieldNames = null;
322 
323         // translate request parameters
324         Map<String, String> parameters = KRADUtils.translateRequestParameterMap(requestParameters);
325 
326         // are override keys listed in the request? If so, then those need to be
327         // our keys, not the primary key fields for the BO
328         if (!StringUtils.isBlank(parameters.get(KRADConstants.OVERRIDE_KEYS))) {
329             String[] overrideKeys =
330                     parameters.get(KRADConstants.OVERRIDE_KEYS).split(KRADConstants.FIELD_CONVERSIONS_SEPARATOR);
331             keyFieldNames = Arrays.asList(overrideKeys);
332         } else {
333             keyFieldNames = getDataObjectMetaDataService().listPrimaryKeyFieldNames(dataObjectClass);
334         }
335 
336         return KRADUtils.getParametersFromRequest(keyFieldNames, dataObjectClass, parameters);
337     }
338 
339     /**
340      * Looks for a special request parameters giving the names of the keys that
341      * should be retrieved from the request parameters and copied to the
342      * maintenance object
343      *
344      * @param parameters - map of parameters from the request
345      * @param oldBusinessObject - the old maintenance object
346      * @param oldMaintainableObject - the old maintainble object (used to get object class for
347      * security checks)
348      */
349     protected void populateMaintenanceObjectWithCopyKeyValues(Map<String, String> parameters, Object oldBusinessObject,
350             Maintainable oldMaintainableObject) {
351         List<String> keyFieldNamesToCopy = null;
352         Map<String, String> parametersToCopy = null;
353 
354         if (!StringUtils.isBlank(parameters.get(KRADConstants.COPY_KEYS))) {
355             String[] copyKeys =
356                     parameters.get(KRADConstants.COPY_KEYS).split(KRADConstants.FIELD_CONVERSIONS_SEPARATOR);
357             keyFieldNamesToCopy = Arrays.asList(copyKeys);
358             parametersToCopy = KRADUtils
359                     .getParametersFromRequest(keyFieldNamesToCopy, oldMaintainableObject.getDataObjectClass(),
360                             parameters);
361         }
362 
363         if (parametersToCopy != null) {
364             // TODO: make sure we are doing formatting here eventually
365             ObjectPropertyUtils.copyPropertiesToObject(parametersToCopy, oldBusinessObject);
366         }
367     }
368 
369     /**
370      * @see org.kuali.rice.krad.service.MaintenanceDocumentService#getLockingDocumentId(org.kuali.rice.krad.maintenance.MaintenanceDocument)
371      */
372     public String getLockingDocumentId(MaintenanceDocument document) {
373         return getLockingDocumentId(document.getNewMaintainableObject(), document.getDocumentNumber());
374     }
375 
376     /**
377      * @see org.kuali.rice.krad.service.MaintenanceDocumentService#getLockingDocumentId(org.kuali.rice.krad.maintenance.Maintainable,
378      *      java.lang.String)
379      */
380     public String getLockingDocumentId(Maintainable maintainable, String documentNumber) {
381         String lockingDocId = null;
382         List<MaintenanceLock> maintenanceLocks = maintainable.generateMaintenanceLocks();
383         for (MaintenanceLock maintenanceLock : maintenanceLocks) {
384             lockingDocId = maintenanceDocumentDao
385                     .getLockingDocumentNumber(maintenanceLock.getLockingRepresentation(), documentNumber);
386             if (StringUtils.isNotBlank(lockingDocId)) {
387                 break;
388             }
389         }
390         return lockingDocId;
391     }
392 
393     /**
394      * @see org.kuali.rice.krad.service.MaintenanceDocumentService#deleteLocks(String)
395      */
396     public void deleteLocks(String documentNumber) {
397         maintenanceDocumentDao.deleteLocks(documentNumber);
398     }
399 
400     /**
401      * @see org.kuali.rice.krad.service.MaintenanceDocumentService#saveLocks(List)
402      */
403     public void storeLocks(List<MaintenanceLock> maintenanceLocks) {
404         maintenanceDocumentDao.storeLocks(maintenanceLocks);
405     }
406 
407     public MaintenanceDocumentDao getMaintenanceDocumentDao() {
408         return maintenanceDocumentDao;
409     }
410 
411     public void setMaintenanceDocumentDao(MaintenanceDocumentDao maintenanceDocumentDao) {
412         this.maintenanceDocumentDao = maintenanceDocumentDao;
413     }
414 
415     protected DataObjectAuthorizationService getDataObjectAuthorizationService() {
416         if (dataObjectAuthorizationService == null) {
417             this.dataObjectAuthorizationService = KRADServiceLocatorWeb.getDataObjectAuthorizationService();
418         }
419         return dataObjectAuthorizationService;
420     }
421 
422     public void setDataObjectAuthorizationService(DataObjectAuthorizationService dataObjectAuthorizationService) {
423         this.dataObjectAuthorizationService = dataObjectAuthorizationService;
424     }
425 
426     protected DocumentService getDocumentService() {
427         return this.documentService;
428     }
429 
430     public void setDocumentService(DocumentService documentService) {
431         this.documentService = documentService;
432     }
433 
434     protected DataObjectMetaDataService getDataObjectMetaDataService() {
435         if (dataObjectMetaDataService == null) {
436             dataObjectMetaDataService = KRADServiceLocatorWeb.getDataObjectMetaDataService();
437         }
438         return dataObjectMetaDataService;
439     }
440 
441     public void setDataObjectMetaDataService(DataObjectMetaDataService dataObjectMetaDataService) {
442         this.dataObjectMetaDataService = dataObjectMetaDataService;
443     }
444 
445     public DocumentDictionaryService getDocumentDictionaryService() {
446         if (documentDictionaryService == null) {
447             this.documentDictionaryService = KRADServiceLocatorWeb.getDocumentDictionaryService();
448         }
449         return documentDictionaryService;
450     }
451 
452     public void setDocumentDictionaryService(DocumentDictionaryService documentDictionaryService) {
453         this.documentDictionaryService = documentDictionaryService;
454     }
455 }