View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors. 
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   *
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.jcr;
25  
26  import java.security.AccessControlException;
27  import java.util.HashSet;
28  import java.util.Iterator;
29  import java.util.LinkedList;
30  import java.util.Set;
31  import java.util.UUID;
32  import javax.jcr.AccessDeniedException;
33  import javax.jcr.InvalidItemStateException;
34  import javax.jcr.PathNotFoundException;
35  import javax.jcr.RepositoryException;
36  import javax.jcr.lock.Lock;
37  import javax.jcr.lock.LockException;
38  import javax.jcr.lock.LockManager;
39  import org.modeshape.common.util.CheckArg;
40  import org.modeshape.graph.session.GraphSession.Node;
41  import org.modeshape.jcr.SessionCache.JcrNodePayload;
42  import org.modeshape.jcr.SessionCache.JcrPropertyPayload;
43  import org.modeshape.jcr.WorkspaceLockManager.ModeShapeLock;
44  
45  /**
46   * A per-session lock manager for a given workspace. This class encapsulates the session-specific locking logic and checks that do
47   * not occur in @{link WorkspaceLockManager}.
48   */
49  public class JcrLockManager implements LockManager {
50  
51      private final JcrSession session;
52      private final WorkspaceLockManager lockManager;
53      private final Set<String> lockTokens;
54  
55      JcrLockManager( JcrSession session,
56                      WorkspaceLockManager lockManager ) {
57          this.session = session;
58          this.lockManager = lockManager;
59          lockTokens = new HashSet<String>();
60      }
61  
62      @Override
63      public void addLockToken( String lockToken ) throws LockException {
64          CheckArg.isNotNull(lockToken, "lock token");
65  
66          // Trivial case of giving a token back to ourself
67          if (lockTokens.contains(lockToken)) {
68              return;
69          }
70  
71          if (lockManager.isHeldBySession(session, lockToken)) {
72              throw new LockException(JcrI18n.lockTokenAlreadyHeld.text(lockToken));
73          }
74  
75          lockManager.setHeldBySession(session, lockToken, true);
76          lockTokens.add(lockToken);
77      }
78  
79      @Override
80      public Lock getLock( String absPath ) throws PathNotFoundException, LockException, AccessDeniedException, RepositoryException {
81          AbstractJcrNode node = session.getNode(absPath);
82          return getLock(node);
83      }
84  
85      Lock getLock( AbstractJcrNode node ) throws PathNotFoundException, LockException, AccessDeniedException, RepositoryException {
86          WorkspaceLockManager.ModeShapeLock lock = lockFor(node);
87          if (lock != null) return lock.lockFor(node.cache);
88          throw new LockException(JcrI18n.notLocked.text(node.location()));
89      }
90  
91      @Override
92      public String[] getLockTokens() {
93          Set<String> publicTokens = new HashSet<String>(lockTokens);
94  
95          for (Iterator<String> iter = publicTokens.iterator(); iter.hasNext();) {
96              String token = iter.next();
97              WorkspaceLockManager.ModeShapeLock lock = lockManager.lockFor(token);
98              if (lock.isSessionScoped()) iter.remove();
99          }
100 
101         return publicTokens.toArray(new String[publicTokens.size()]);
102     }
103 
104     Set<String> lockTokens() {
105         return this.lockTokens;
106     }
107 
108     @Override
109     public boolean holdsLock( String absPath ) throws PathNotFoundException, RepositoryException {
110         AbstractJcrNode node = session.getNode(absPath);
111         return holdsLock(node);
112     }
113 
114     boolean holdsLock( AbstractJcrNode node ) {
115         WorkspaceLockManager.ModeShapeLock lock = lockManager.lockFor(session, node.location());
116 
117         return lock != null;
118 
119     }
120 
121     @Override
122     public boolean isLocked( String absPath ) throws PathNotFoundException, RepositoryException {
123         AbstractJcrNode node = session.getNode(absPath);
124         return isLocked(node);
125     }
126 
127     boolean isLocked( AbstractJcrNode node ) throws PathNotFoundException, RepositoryException {
128         return lockFor(node) != null;
129     }
130 
131     @Override
132     public Lock lock( String absPath,
133                       boolean isDeep,
134                       boolean isSessionScoped,
135                       long timeoutHint,
136                       String ownerInfo )
137         throws LockException, PathNotFoundException, AccessDeniedException, InvalidItemStateException, RepositoryException {
138         AbstractJcrNode node = session.getNode(absPath);
139         return lock(node, isDeep, isSessionScoped, timeoutHint, ownerInfo);
140     }
141 
142     Lock lock( AbstractJcrNode node,
143                boolean isDeep,
144                boolean isSessionScoped,
145                long timeoutHint,
146                String ownerInfo )
147         throws LockException, PathNotFoundException, AccessDeniedException, InvalidItemStateException, RepositoryException {
148         if (!node.isLockable()) {
149             throw new LockException(JcrI18n.nodeNotLockable.text(node.getPath()));
150         }
151 
152         if (node.isLocked()) {
153             throw new LockException(JcrI18n.alreadyLocked.text(node.location()));
154         }
155 
156         if (node.isModified()) {
157             throw new InvalidItemStateException();
158         }
159 
160         if (isDeep) {
161             LinkedList<Node<JcrNodePayload, JcrPropertyPayload>> nodesToVisit = new LinkedList<Node<JcrNodePayload, JcrPropertyPayload>>();
162             nodesToVisit.add(node.nodeInfo());
163 
164             while (!nodesToVisit.isEmpty()) {
165                 Node<JcrNodePayload, JcrPropertyPayload> graphNode = nodesToVisit.remove(nodesToVisit.size() - 1);
166                 if (lockManager.lockFor(session, graphNode.getLocation()) != null) throw new LockException(
167                                                                                                            JcrI18n.parentAlreadyLocked.text(node.location,
168                                                                                                                                             graphNode.getLocation()));
169 
170                 for (Node<JcrNodePayload, JcrPropertyPayload> child : graphNode.getChildren()) {
171                     nodesToVisit.add(child);
172                 }
173             }
174         }
175 
176         WorkspaceLockManager.ModeShapeLock lock = lockManager.lock(session, node.location(), isDeep, isSessionScoped);
177 
178         addLockToken(lock.getLockToken());
179         return lock.lockFor(session.cache());
180 
181     }
182 
183     @Override
184     public void removeLockToken( String lockToken ) throws LockException {
185         CheckArg.isNotNull(lockToken, "lockToken");
186         // A LockException is thrown if the lock associated with the specified lock token is session-scoped.
187 
188         if (!lockTokens.contains(lockToken)) {
189             throw new LockException(JcrI18n.invalidLockToken.text(lockToken));
190         }
191 
192         /*
193          * The JCR API library that we're using diverges from the spec in that it doesn't declare
194          * this method to throw a LockException.  We'll throw a runtime exception for now.
195          */
196 
197         ModeShapeLock lock = lockManager.lockFor(lockToken);
198         if (lock == null) {
199             // The lock is no longer valid
200             lockTokens.remove(lockToken);
201             return;
202         }
203 
204         if (lock.isSessionScoped()) {
205             throw new IllegalStateException(JcrI18n.cannotRemoveLockToken.text(lockToken));
206         }
207 
208         lockManager.setHeldBySession(session, lockToken, false);
209         lockTokens.remove(lockToken);
210     }
211 
212     @Override
213     public void unlock( String absPath )
214         throws PathNotFoundException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException {
215         AbstractJcrNode node = session.getNode(absPath);
216         unlock(node);
217     }
218 
219     @SuppressWarnings( "unused" )
220     void unlock( AbstractJcrNode node )
221         throws PathNotFoundException, LockException, AccessDeniedException, InvalidItemStateException, RepositoryException {
222         WorkspaceLockManager.ModeShapeLock lock = lockManager.lockFor(session, node.location());
223 
224         if (lock == null) {
225             throw new LockException(JcrI18n.notLocked.text(node.location()));
226         }
227 
228         if (lockTokens.contains(lock.getLockToken())) {
229             lockManager.unlock(session.getExecutionContext(), lock);
230             removeLockToken(lock.getLockToken());
231         } else {
232             try {
233                 // See if the user has the permission to break someone else's lock
234                 session.checkPermission(session.cache().workspaceName(), null, ModeShapePermissions.UNLOCK_ANY);
235 
236                 // This user doesn't have the lock token, so don't try to remove it
237                 lockManager.unlock(session.getExecutionContext(), lock);
238             } catch (AccessControlException iae) {
239                 throw new LockException(JcrI18n.lockTokenNotHeld.text(node.location()));
240             }
241         }
242 
243     }
244 
245     /**
246      * 
247      */
248     final void cleanLocks() {
249         lockManager.cleanLocks(session);
250     }
251 
252     final WorkspaceLockManager.ModeShapeLock lockFor( AbstractJcrNode node ) throws RepositoryException {
253         // This can only happen in mocked testing.
254         if (session == null || session.workspace() == null) return null;
255 
256         WorkspaceLockManager.ModeShapeLock lock = lockManager.lockFor(session, node.location());
257         if (lock != null) return lock;
258 
259         AbstractJcrNode parent = node;
260         while (!parent.isRoot()) {
261             parent = parent.getParent();
262 
263             WorkspaceLockManager.ModeShapeLock parentLock = lockManager.lockFor(session, parent.location());
264             if (parentLock != null && parentLock.isLive()) {
265                 return parentLock.isDeep() ? parentLock : null;
266             }
267         }
268         return null;
269     }
270 
271     final WorkspaceLockManager.ModeShapeLock lockFor( UUID nodeUuid ) {
272         // This can only happen in mocked testing.
273         if (session == null || session.workspace() == null) return null;
274 
275         return lockManager.lockFor(nodeUuid);
276     }
277 
278 }