View Javadoc

1   /*
2    * Copyright 2007-2008 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 org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.util.RiceConstants;
20  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
21  import org.kuali.rice.kim.bo.Person;
22  import org.kuali.rice.kim.service.PermissionService;
23  import org.kuali.rice.kim.service.PersonService;
24  import org.kuali.rice.kim.util.KimConstants.PermissionNames;
25  import org.kuali.rice.krad.authorization.AuthorizationConstants;
26  import org.kuali.rice.krad.document.Document;
27  import org.kuali.rice.krad.document.authorization.PessimisticLock;
28  import org.kuali.rice.krad.exception.AuthorizationException;
29  import org.kuali.rice.krad.exception.PessimisticLockingException;
30  import org.kuali.rice.krad.service.BusinessObjectService;
31  import org.kuali.rice.krad.service.DataDictionaryService;
32  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
33  import org.kuali.rice.krad.service.PessimisticLockService;
34  import org.kuali.rice.krad.util.GlobalVariables;
35  import org.kuali.rice.krad.util.KRADConstants;
36  import org.kuali.rice.krad.util.KRADPropertyConstants;
37  import org.kuali.rice.krad.util.ObjectUtils;
38  import org.springframework.transaction.annotation.Transactional;
39  
40  import java.util.ArrayList;
41  import java.util.HashMap;
42  import java.util.HashSet;
43  import java.util.Iterator;
44  import java.util.List;
45  import java.util.Map;
46  import java.util.Set;
47  
48  /**
49   * This is a service implementation for pessimistic locking
50   *
51   * @author Kuali Rice Team (rice.collab@kuali.org)
52   *
53   */
54  @Transactional
55  public class PessimisticLockServiceImpl implements PessimisticLockService {
56      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PessimisticLockServiceImpl.class);
57  
58      private PersonService personService;
59      private BusinessObjectService businessObjectService;
60      private DataDictionaryService dataDictionaryService;
61      private PermissionService permissionService;
62  
63      /**
64       * @see org.kuali.rice.krad.service.PessimisticLockService#delete(java.lang.String)
65       */
66      public void delete(String id) {
67          if (StringUtils.isBlank(id)) {
68              throw new IllegalArgumentException("An invalid blank id was passed to delete a Pessimistic Lock.");
69          }
70          Map<String,Object> primaryKeys = new HashMap<String,Object>();
71          primaryKeys.put(KRADPropertyConstants.ID, Long.valueOf(id));
72          PessimisticLock lock = (PessimisticLock) getBusinessObjectService().findByPrimaryKey(PessimisticLock.class, primaryKeys);
73          if (ObjectUtils.isNull(lock)) {
74              throw new IllegalArgumentException("Pessimistic Lock with id " + id + " cannot be found in the database.");
75          }
76          Person user = GlobalVariables.getUserSession().getPerson();
77          if ( (!lock.isOwnedByUser(user)) && (!isPessimisticLockAdminUser(user)) ) {
78              throw new AuthorizationException(user.getName(),"delete", "Pessimistick Lock (id " + id + ")");
79          }
80          delete(lock);
81      }
82  
83      private void delete(PessimisticLock lock) {
84      	if ( LOG.isDebugEnabled() ) {
85      		LOG.debug("Deleting lock: " + lock);
86      	}
87          getBusinessObjectService().delete(lock);
88      }
89  
90      /**
91       * @see org.kuali.rice.krad.service.PessimisticLockService#generateNewLock(String)
92       */
93      public PessimisticLock generateNewLock(String documentNumber) {
94          return generateNewLock(documentNumber, GlobalVariables.getUserSession().getPerson());
95      }
96  
97      /**
98       * @see org.kuali.rice.krad.service.PessimisticLockService#generateNewLock(java.lang.String)
99       */
100     public PessimisticLock generateNewLock(String documentNumber, String lockDescriptor) {
101         return generateNewLock(documentNumber, lockDescriptor, GlobalVariables.getUserSession().getPerson());
102     }
103 
104     /**
105      * @see org.kuali.rice.krad.service.PessimisticLockService#generateNewLock(java.lang.String, org.kuali.rice.kim.bo.Person)
106      */
107     public PessimisticLock generateNewLock(String documentNumber, Person user) {
108         return generateNewLock(documentNumber, PessimisticLock.DEFAULT_LOCK_DESCRIPTOR, user);
109     }
110 
111     /**
112      * @see org.kuali.rice.krad.service.PessimisticLockService#generateNewLock(java.lang.String, java.lang.String, org.kuali.rice.kim.bo.Person)
113      */
114     public PessimisticLock generateNewLock(String documentNumber, String lockDescriptor, Person user) {
115         PessimisticLock lock = new PessimisticLock(documentNumber, lockDescriptor, user);
116         lock = save(lock);
117         if ( LOG.isDebugEnabled() ) {
118         	LOG.debug("Generated new lock: " + lock);
119         }
120         return lock;
121     }
122 
123     /**
124      * @see org.kuali.rice.krad.service.PessimisticLockService#getPessimisticLocksForDocument(java.lang.String)
125      */
126     @SuppressWarnings("unchecked")
127     public List<PessimisticLock> getPessimisticLocksForDocument(String documentNumber) {
128         Map fieldValues = new HashMap();
129         fieldValues.put(KRADPropertyConstants.DOCUMENT_NUMBER, documentNumber);
130         return (List<PessimisticLock>) getBusinessObjectService().findMatching(PessimisticLock.class, fieldValues);
131     }
132 
133     /**
134      * @see org.kuali.rice.krad.service.PessimisticLockService#isPessimisticLockAdminUser(org.kuali.rice.kim.bo.Person)
135      */
136     public boolean isPessimisticLockAdminUser(Person user) {
137     	return getPermissionService().isAuthorized( user.getPrincipalId(), KRADConstants.KRAD_NAMESPACE, PermissionNames.ADMIN_PESSIMISTIC_LOCKING, null, null );
138     }
139 
140     /**
141      * @see org.kuali.rice.krad.service.PessimisticLockService#releaseAllLocksForUser(java.util.List, org.kuali.rice.kim.bo.Person)
142      */
143     public void releaseAllLocksForUser(List<PessimisticLock> locks, Person user) {
144         for (Iterator<PessimisticLock> iterator = locks.iterator(); iterator.hasNext();) {
145             PessimisticLock lock = (PessimisticLock) iterator.next();
146             if (lock.isOwnedByUser(user)) {
147                 delete(lock);
148             }
149         }
150     }
151 
152     /**
153      * @see org.kuali.rice.krad.service.PessimisticLockService#releaseAllLocksForUser(java.util.List, org.kuali.rice.kim.bo.Person, java.lang.String)
154      */
155     public void releaseAllLocksForUser(List<PessimisticLock> locks, Person user, String lockDescriptor) {
156         for (Iterator<PessimisticLock> iterator = locks.iterator(); iterator.hasNext();) {
157             PessimisticLock lock = (PessimisticLock) iterator.next();
158             if ( (lock.isOwnedByUser(user)) && (lockDescriptor.equals(lock.getLockDescriptor())) ) {
159                 delete(lock);
160             }
161         }
162     }
163 
164     /**
165      * @see org.kuali.rice.krad.service.PessimisticLockService#save(org.kuali.rice.krad.document.authorization.PessimisticLock)
166      */
167     public PessimisticLock save(PessimisticLock lock) {
168     	if ( LOG.isDebugEnabled() ) {
169     		LOG.debug("Saving lock: " + lock);
170     	}
171         return (PessimisticLock)getBusinessObjectService().save(lock);
172     }
173 
174     public BusinessObjectService getBusinessObjectService() {
175         return this.businessObjectService;
176     }
177 
178     public void setBusinessObjectService(BusinessObjectService businessObjectService) {
179         this.businessObjectService = businessObjectService;
180     }
181 
182     /**
183      * @param document
184      * @param user
185      * @return Set of actions are permitted the given user on the given document
186      */
187     public Set getDocumentActions(Document document, Person user, Set<String> documentActions){
188     	if(documentActions.contains(KRADConstants.KUALI_ACTION_CAN_CANCEL) && !hasPreRouteEditAuthorization(document, user) ){
189     		documentActions.remove(KRADConstants.KUALI_ACTION_CAN_CANCEL);
190     	}
191     	if(documentActions.contains(KRADConstants.KUALI_ACTION_CAN_SAVE)  && !hasPreRouteEditAuthorization(document, user)){
192     		documentActions.remove(KRADConstants.KUALI_ACTION_CAN_SAVE);
193     	}
194         if(documentActions.contains(KRADConstants.KUALI_ACTION_CAN_ROUTE) && !hasPreRouteEditAuthorization(document, user)){
195         	documentActions.remove(KRADConstants.KUALI_ACTION_CAN_ROUTE);
196         }
197         if (documentActions.contains(KRADConstants.KUALI_ACTION_CAN_BLANKET_APPROVE) && !hasPreRouteEditAuthorization(document, user)){
198         	documentActions.remove(KRADConstants.KUALI_ACTION_CAN_BLANKET_APPROVE);
199         }
200     	return documentActions;
201     }
202 
203 
204     /**
205      * This method checks to see that the given user has a lock on the document and return true if one is found.
206      *
207      * @param document - document to check
208      * @param user - current user
209      * @return true if the document is using Pessimistic Locking, the user has initiate authorization (see
210      *         {@link #hasInitiateAuthorization(Document, Person)}), and the document has a lock owned by the given
211      *         user. If the document is not using Pessimistic Locking the value returned will be that returned by
212      *         {@link #hasInitiateAuthorization(Document, Person)}.
213      */
214     protected boolean hasPreRouteEditAuthorization(Document document, Person user) {
215     	if (document.getPessimisticLocks().isEmpty()) {
216     		return true;
217     	}
218     	for (Iterator iterator = document.getPessimisticLocks().iterator(); iterator.hasNext();) {
219     		PessimisticLock lock = (PessimisticLock) iterator.next();
220     		if (lock.isOwnedByUser(user)) {
221     			return true;
222             }
223         }
224         return false;
225     }
226 
227 
228     protected boolean usesPessimisticLocking(Document document) {
229         return getDataDictionaryService().getDataDictionary().getDocumentEntry(document.getClass().getName()).getUsePessimisticLocking();
230     }
231 
232 
233     /**
234      * This method creates a new {@link PessimisticLock} when Workflow processing requires one
235      *
236      * @param document - the document to create the lock against and add the lock to
237      * @see org.kuali.rice.krad.document.authorization.DocumentAuthorizer#establishWorkflowPessimisticLocking(org.kuali.rice.krad.document.Document)
238      */
239     public void establishWorkflowPessimisticLocking(Document document) {
240         PessimisticLock lock = createNewPessimisticLock(document, new HashMap(), getWorkflowPessimisticLockOwnerUser());
241         document.addPessimisticLock(lock);
242     }
243 
244     /**
245      * This method releases locks created via the {@link #establishWorkflowPessimisticLocking(Document)} method for the given document
246      *
247      * @param document - document to release locks from
248      * @see org.kuali.rice.krad.document.authorization.DocumentAuthorizer#releaseWorkflowPessimisticLocking(org.kuali.rice.krad.document.Document)
249      */
250     public void releaseWorkflowPessimisticLocking(Document document) {
251         releaseAllLocksForUser(document.getPessimisticLocks(), getWorkflowPessimisticLockOwnerUser());
252         document.refreshPessimisticLocks();
253     }
254 
255     /**
256      * This method identifies the user that should be used to create and clear {@link PessimisticLock} objects required by
257      * Workflow.<br>
258      * <br>
259      * The default is the Kuali system user defined by {@link RiceConstants#SYSTEM_USER}. This method can be overriden by
260      * implementing documents if another user is needed.
261      *
262      * @return a valid {@link Person} object
263      */
264     protected Person getWorkflowPessimisticLockOwnerUser() {
265         String networkId = KRADConstants.SYSTEM_USER;
266         return getPersonService().getPersonByPrincipalName(networkId);
267     }
268 
269     /**
270      * This implementation will check the given document, editMode map, and user object to verify Pessimistic Locking. If the
271      * given edit mode map contains an 'entry type' edit mode then the system will check the locks already in existence on
272      * the document. If a valid lock for the given user is found the system will return the given edit mode map. If a valid
273      * lock is found but is owned by another user the edit mode map returned will have any 'entry type' edit modes removed. If the
274      * given document has no locks and the edit mode map passed in has at least one 'entry type' mode then a new
275      * {@link PessimisticLock} object will be created and set on the document for the given user.<br>
276      * <br>
277      * NOTE: This method is only called if the document uses pessimistic locking as described in the data dictionary file.
278      *
279      * @see org.kuali.rice.krad.document.authorization.DocumentAuthorizer#establishLocks(org.kuali.rice.krad.document.Document,
280      *      java.util.Map, org.kuali.rice.kim.bo.Person)
281      */
282     public Map establishLocks(Document document, Map editMode, Person user) {
283         Map editModeMap = new HashMap();
284         // givenUserLockDescriptors is a list of lock descriptors currently held on the document by the given user
285         List<String> givenUserLockDescriptors = new ArrayList<String>();
286         // lockDescriptorUsers is a map with lock descriptors as keys and users other than the given user who hold a lock of each descriptor
287         Map<String,Set<Person>> lockDescriptorUsers = new HashMap<String,Set<Person>>();
288 
289         // build the givenUserLockDescriptors set and the lockDescriptorUsers map
290         for (PessimisticLock lock : document.getPessimisticLocks()) {
291             if (lock.isOwnedByUser(user)) {
292                 // lock is owned by given user
293                 givenUserLockDescriptors.add(lock.getLockDescriptor());
294             } else {
295                 // lock is not owned by the given user
296                 if (!lockDescriptorUsers.containsKey(lock.getLockDescriptor())) {
297                     lockDescriptorUsers.put(lock.getLockDescriptor(), new HashSet<Person>());
298                 }
299                 ((Set<Person>) lockDescriptorUsers.get(lock.getLockDescriptor())).add(lock.getOwnedByUser());
300             }
301         }
302 
303         // verify that no locks held by current user exist for any other user
304         for (String givenUserLockDescriptor : givenUserLockDescriptors) {
305             if ( (lockDescriptorUsers.containsKey(givenUserLockDescriptor)) && (lockDescriptorUsers.get(givenUserLockDescriptor).size() > 0) ) {
306                 Set<Person> users = lockDescriptorUsers.get(givenUserLockDescriptor);
307                 if ( (users.size() != 1) || (!getWorkflowPessimisticLockOwnerUser().getPrincipalId().equals(users.iterator().next().getPrincipalId())) ) {
308                     String descriptorText = (document.useCustomLockDescriptors()) ? " using lock descriptor '" + givenUserLockDescriptor + "'" : "";
309                     String errorMsg = "Found an invalid lock status on document number " + document.getDocumentNumber() + "with current user and other user both having locks" + descriptorText + " concurrently";
310                     LOG.debug(errorMsg);
311                     throw new PessimisticLockingException(errorMsg);
312                 }
313             }
314         }
315 
316         // check to see if the given user has any locks in the system at all
317         if (givenUserLockDescriptors.isEmpty()) {
318             // given user has no locks... check for other user locks
319             if (lockDescriptorUsers.isEmpty()) {
320                 // no other user has any locks... set up locks for given user if user has edit privileges
321                 if (isLockRequiredByUser(document, editMode, user)) {
322                     document.addPessimisticLock(createNewPessimisticLock(document, editMode, user));
323                 }
324                 editModeMap.putAll(editMode);
325             } else {
326                 // at least one other user has at least one other lock... adjust edit mode for read only
327                 if (document.useCustomLockDescriptors()) {
328                     // check to see if the custom lock descriptor is already in use
329                     String customLockDescriptor = document.getCustomLockDescriptor(user);
330                     if (lockDescriptorUsers.containsKey(customLockDescriptor)) {
331                         // at least one other user has this descriptor locked... remove editable edit modes
332                         editModeMap = getEditModeWithEditableModesRemoved(editMode);
333                     } else {
334                         // no other user has a lock with this descriptor
335                         if (isLockRequiredByUser(document, editMode, user)) {
336                             document.addPessimisticLock(createNewPessimisticLock(document, editMode, user));
337                         }
338                         editModeMap.putAll(editMode);
339                     }
340                 } else {
341                     editModeMap = getEditModeWithEditableModesRemoved(editMode);
342                 }
343             }
344         } else {
345             // given user already has at least one lock descriptor
346             if (document.useCustomLockDescriptors()) {
347                 // get the custom lock descriptor and check to see if if the given user has a lock with that descriptor
348                 String customLockDescriptor = document.getCustomLockDescriptor(user);
349                 if (givenUserLockDescriptors.contains(customLockDescriptor)) {
350                     // user already has lock that is required
351                     editModeMap.putAll(editMode);
352                 } else {
353                     // user does not have lock for descriptor required
354                     if (lockDescriptorUsers.containsKey(customLockDescriptor)) {
355                         // another user has the lock descriptor that the given user requires... disallow lock and alter edit modes to have read only
356                         editModeMap = getEditModeWithEditableModesRemoved(editMode);
357                     } else {
358                         // no other user has a lock with this descriptor... check if this user needs a lock
359                         if (isLockRequiredByUser(document, editMode, user)) {
360                             document.addPessimisticLock(createNewPessimisticLock(document, editMode, user));
361                         }
362                         editModeMap.putAll(editMode);
363                     }
364                 }
365             } else {
366                 // user already has lock and no descriptors are being used... use the existing edit modes
367                 editModeMap.putAll(editMode);
368             }
369         }
370 
371         return editModeMap;
372     }
373 
374     /**
375      * This method is used to check if the given parameters warrant a new lock to be created for the given user. This method
376      * utilizes the {@link #isEntryEditMode(java.util.Map.Entry)} method.
377      *
378      * @param document -
379      *            document to verify lock creation against
380      * @param editMode -
381      *            edit modes list to check for 'entry type' edit modes
382      * @param user -
383      *            user the lock will be 'owned' by
384      * @return true if the given edit mode map has at least one 'entry type' edit mode... false otherwise
385      */
386     protected boolean isLockRequiredByUser(Document document, Map editMode, Person user) {
387         // check for entry edit mode
388         for (Iterator iterator = editMode.entrySet().iterator(); iterator.hasNext();) {
389             Map.Entry entry = (Map.Entry) iterator.next();
390             if (isEntryEditMode(entry)) {
391                 return true;
392             }
393         }
394         return false;
395     }
396 
397    /**
398      * This method is used to remove edit modes from the given map that allow the user to edit data on the document. This
399      * method utilizes the {@link #isEntryEditMode(java.util.Map.Entry)} method to identify if an edit mode is defined as an
400      * 'entry type' edit mode. It also uses the {@link #getEntryEditModeReplacementMode(java.util.Map.Entry)} method to replace
401      * any 'entry type' edit modes it finds.
402      *
403      * @param currentEditMode -
404      *            current set of edit modes the user has assigned to them
405      * @return an adjusted edit mode map where 'entry type' edit modes have been removed or replaced using the
406      *         {@link #getEntryEditModeReplacementMode()} method
407      */
408     protected Map getEditModeWithEditableModesRemoved(Map currentEditMode) {
409         Map editModeMap = new HashMap();
410         for (Iterator iterator = currentEditMode.entrySet().iterator(); iterator.hasNext();) {
411             Map.Entry entry = (Map.Entry) iterator.next();
412             if (isEntryEditMode(entry)) {
413                 editModeMap.putAll(getEntryEditModeReplacementMode(entry));
414             } else {
415                 editModeMap.put(entry.getKey(), entry.getValue());
416             }
417         }
418         return editModeMap;
419     }
420 
421     /**
422      * This method is used to check if the given {@link Map.Entry} is an 'entry type' edit mode and that the value is set to
423      * signify that this user has that edit mode available to them
424      *
425      * @param entry -
426      *            the {@link Map.Entry} object that contains an edit mode such as the ones returned but
427      *            {@link #getEditMode(Document, Person)}
428      * @return true if the given entry has a key signifying an 'entry type' edit mode and the value is equal to
429      *         {@link #EDIT_MODE_DEFAULT_TRUE_VALUE}... false if not
430      */
431     protected boolean isEntryEditMode(Map.Entry entry) {
432     	// check for FULL_ENTRY edit mode set to default true value
433     	if (AuthorizationConstants.EditMode.FULL_ENTRY.equals(entry.getKey())) {
434     		String fullEntryEditModeValue = (String)entry.getValue();           
435     		return ( StringUtils.equalsIgnoreCase(KRADConstants.KUALI_DEFAULT_TRUE_VALUE, fullEntryEditModeValue) );
436     	}
437     	return false;
438     }
439 
440     /**
441      * This method is used to return values needed to replace the given 'entry type' edit mode {@link Map.Entry} with one that will not allow the user to enter data on the document
442      *
443      * @param entry - the current 'entry type' edit mode to replace
444      * @return a Map of edit modes that will be used to replace this edit mode (represented by the given entry parameter)
445      */
446     protected Map getEntryEditModeReplacementMode(Map.Entry entry) {
447         Map editMode = new HashMap();
448         editMode.put(AuthorizationConstants.EditMode.VIEW_ONLY, KRADConstants.KUALI_DEFAULT_TRUE_VALUE);
449         return editMode;
450     }
451 
452     /**
453      * This method creates a new {@link PessimisticLock} object using the given document and user. If the document's
454      * useCustomLockDescriptors() method returns true then the new lock will also have a custom lock descriptor
455      * value set to the return value of the document's getCustomLockDescriptor(Person) method.
456      *
457      * @param document -
458      *            document to place the lock on
459      * @param editMode -
460      *            current edit modes for given user
461      * @param user -
462      *            user who will 'own' the new lock object
463      * @return the newly created lock object
464      */
465     protected PessimisticLock createNewPessimisticLock(Document document, Map editMode, Person user) {
466         if (document.useCustomLockDescriptors()) {
467             return generateNewLock(document.getDocumentNumber(), document.getCustomLockDescriptor(user), user);
468         } else {
469             return generateNewLock(document.getDocumentNumber(), user);
470         }
471     }
472 
473     public PersonService getPersonService() {
474         if ( personService == null ) {
475             personService = KimApiServiceLocator.getPersonService();
476         }
477         return personService;
478     }
479 
480 	public DataDictionaryService getDataDictionaryService() {
481         if ( dataDictionaryService == null ) {
482         	dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService();
483         }
484 		return dataDictionaryService;
485 	}
486 
487 	public PermissionService getPermissionService() {
488         if ( permissionService == null ) {
489         	permissionService = KimApiServiceLocator.getPermissionService();
490         }
491 		return permissionService;
492 	}
493 
494 
495 
496 }
497