1 package org.modeshape.jcr;
2
3 import java.util.Collection;
4 import java.util.UUID;
5 import java.util.concurrent.ConcurrentHashMap;
6 import java.util.concurrent.ConcurrentMap;
7 import javax.jcr.Item;
8 import javax.jcr.Node;
9 import javax.jcr.PropertyType;
10 import javax.jcr.RepositoryException;
11 import javax.jcr.Session;
12 import javax.jcr.lock.Lock;
13 import javax.jcr.lock.LockException;
14 import net.jcip.annotations.ThreadSafe;
15 import org.modeshape.graph.ExecutionContext;
16 import org.modeshape.graph.Graph;
17 import org.modeshape.graph.Location;
18 import org.modeshape.graph.connector.LockFailedException;
19 import org.modeshape.graph.property.DateTime;
20 import org.modeshape.graph.property.DateTimeFactory;
21 import org.modeshape.graph.property.Path;
22 import org.modeshape.graph.property.PathFactory;
23 import org.modeshape.graph.property.PathNotFoundException;
24 import org.modeshape.graph.property.Property;
25 import org.modeshape.graph.property.PropertyFactory;
26 import org.modeshape.graph.property.ValueFactory;
27 import org.modeshape.jcr.SessionCache.NodeEditor;
28
29
30
31
32
33 @ThreadSafe
34 class WorkspaceLockManager {
35
36 private final ExecutionContext context;
37 private final Path locksPath;
38 private final JcrRepository repository;
39 private final String workspaceName;
40 private final ConcurrentMap<UUID, ModeShapeLock> workspaceLocksByNodeUuid;
41
42 WorkspaceLockManager( ExecutionContext context,
43 JcrRepository repository,
44 String workspaceName,
45 Path locksPath ) {
46 this.context = context;
47 this.repository = repository;
48 this.workspaceName = workspaceName;
49 this.locksPath = locksPath;
50
51 this.workspaceLocksByNodeUuid = new ConcurrentHashMap<UUID, ModeShapeLock>();
52
53 Property locksPrimaryType = context.getPropertyFactory().create(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.LOCKS);
54 repository.createSystemGraph(context).create(locksPath, locksPrimaryType).ifAbsent().and();
55 }
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72 ModeShapeLock lock( JcrSession session,
73 Location nodeLocation,
74 boolean isDeep,
75 boolean isSessionScoped ) throws RepositoryException {
76 assert nodeLocation != null;
77
78 UUID lockUuid = UUID.randomUUID();
79 UUID nodeUuid = uuidFor(session, nodeLocation);
80
81 if (nodeUuid == null) {
82 throw new RepositoryException(JcrI18n.uuidRequiredForLock.text(nodeLocation));
83 }
84
85 ExecutionContext sessionContext = session.getExecutionContext();
86 String lockOwner = session.getUserID();
87 ModeShapeLock lock = createLock(lockOwner, lockUuid, nodeUuid, isDeep, isSessionScoped);
88
89 Graph.Batch batch = repository.createSystemGraph(sessionContext).batch();
90
91 PropertyFactory propFactory = sessionContext.getPropertyFactory();
92 PathFactory pathFactory = sessionContext.getValueFactories().getPathFactory();
93 Property lockOwnerProp = propFactory.create(JcrLexicon.LOCK_OWNER, lockOwner);
94 Property lockIsDeepProp = propFactory.create(JcrLexicon.LOCK_IS_DEEP, isDeep);
95
96 DateTimeFactory dateFactory = sessionContext.getValueFactories().getDateFactory();
97 DateTime expirationDate = dateFactory.create();
98 expirationDate = expirationDate.plusMillis(JcrEngine.LOCK_EXTENSION_INTERVAL_IN_MILLIS);
99
100 batch.create(pathFactory.create(locksPath, pathFactory.createSegment(lockUuid.toString())),
101 propFactory.create(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.LOCK),
102 propFactory.create(ModeShapeLexicon.WORKSPACE, workspaceName),
103 propFactory.create(ModeShapeLexicon.LOCKED_UUID, nodeUuid.toString()),
104 propFactory.create(ModeShapeLexicon.IS_SESSION_SCOPED, isSessionScoped),
105 propFactory.create(ModeShapeLexicon.LOCKING_SESSION, session.sessionId()),
106 propFactory.create(ModeShapeLexicon.EXPIRATION_DATE, expirationDate),
107
108 propFactory.create(ModeShapeLexicon.IS_HELD_BY_SESSION, false),
109 lockOwnerProp,
110 lockIsDeepProp).ifAbsent().and();
111 batch.execute();
112
113 SessionCache cache = session.cache();
114 AbstractJcrNode lockedNode = cache.findJcrNode(Location.create(nodeUuid));
115 NodeEditor editor = cache.getEditorFor(lockedNode.nodeInfo());
116
117
118 editor.setProperty(JcrLexicon.LOCK_OWNER,
119 (JcrValue)cache.session().getValueFactory().createValue(lockOwner, PropertyType.STRING),
120 false);
121 editor.setProperty(JcrLexicon.LOCK_IS_DEEP, (JcrValue)cache.session().getValueFactory().createValue(isDeep), false);
122
123 lockNodeInRepository(session, nodeUuid, lockOwnerProp, lockIsDeepProp, lock, isDeep);
124 workspaceLocksByNodeUuid.put(nodeUuid, lock);
125
126 return lock;
127 }
128
129 ModeShapeLock createLock( org.modeshape.graph.Node lockNode ) {
130 return new ModeShapeLock(lockNode);
131 }
132
133
134 ModeShapeLock createLock( String lockOwner,
135 UUID lockUuid,
136 UUID nodeUuid,
137 boolean isDeep,
138 boolean isSessionScoped ) {
139 return new ModeShapeLock(lockOwner, lockUuid, nodeUuid, isDeep, isSessionScoped);
140 }
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170 void lockNodeInRepository( JcrSession session,
171 UUID nodeUuid,
172 Property lockOwnerProp,
173 Property lockIsDeepProp,
174 ModeShapeLock lock,
175 boolean isDeep ) throws RepositoryException {
176
177 Graph.Batch workspaceBatch = repository.createWorkspaceGraph(workspaceName, session.getExecutionContext()).batch();
178 workspaceBatch.set(lockOwnerProp, lockIsDeepProp).on(nodeUuid);
179 if (isDeep) {
180 workspaceBatch.lock(nodeUuid).andItsDescendants().withDefaultTimeout();
181 } else {
182 workspaceBatch.lock(nodeUuid).only().withDefaultTimeout();
183 }
184 try {
185 workspaceBatch.execute();
186 } catch (LockFailedException lfe) {
187
188 unlock(session.getExecutionContext(), lock);
189 throw new RepositoryException(lfe);
190 }
191
192 }
193
194
195
196
197
198
199
200 void unlock( ExecutionContext sessionExecutionContext,
201 ModeShapeLock lock ) {
202 try {
203 PathFactory pathFactory = sessionExecutionContext.getValueFactories().getPathFactory();
204
205
206 Graph.Batch batch = repository.createSystemGraph(sessionExecutionContext).batch();
207 batch.delete(pathFactory.create(locksPath, pathFactory.createSegment(lock.getUuid().toString())));
208 batch.execute();
209
210
211 unlockNodeInRepository(sessionExecutionContext, lock);
212
213 workspaceLocksByNodeUuid.remove(lock.nodeUuid);
214 } catch (PathNotFoundException pnfe) {
215
216
217
218
219 if (!lock.nodeUuid.equals(pnfe.getLocation().getUuid())) {
220
221 throw new IllegalStateException(pnfe);
222 }
223 workspaceLocksByNodeUuid.remove(lock.nodeUuid);
224 }
225 }
226
227
228
229
230
231
232
233
234
235
236
237
238
239 void unlockNodeInRepository( ExecutionContext sessionExecutionContext,
240 ModeShapeLock lock ) {
241 Graph.Batch workspaceBatch = repository.createWorkspaceGraph(this.workspaceName, sessionExecutionContext).batch();
242
243 workspaceBatch.remove(JcrLexicon.LOCK_OWNER, JcrLexicon.LOCK_IS_DEEP).on(lock.nodeUuid);
244 workspaceBatch.unlock(lock.nodeUuid);
245
246 workspaceBatch.execute();
247 }
248
249
250
251
252
253
254
255
256
257 boolean isHeldBySession( JcrSession session,
258 String lockToken ) {
259 assert lockToken != null;
260
261 ExecutionContext context = session.getExecutionContext();
262 ValueFactory<Boolean> booleanFactory = context.getValueFactories().getBooleanFactory();
263 PathFactory pathFactory = context.getValueFactories().getPathFactory();
264
265 org.modeshape.graph.Node lockNode = repository.createSystemGraph(context)
266 .getNodeAt(pathFactory.create(locksPath,
267 pathFactory.createSegment(lockToken)));
268
269 return booleanFactory.create(lockNode.getProperty(ModeShapeLexicon.IS_HELD_BY_SESSION).getFirstValue());
270
271 }
272
273
274
275
276
277
278
279
280
281
282 void setHeldBySession( JcrSession session,
283 String lockToken,
284 boolean value ) {
285 assert lockToken != null;
286
287 ExecutionContext context = session.getExecutionContext();
288 PropertyFactory propFactory = context.getPropertyFactory();
289 PathFactory pathFactory = context.getValueFactories().getPathFactory();
290
291 repository.createSystemGraph(context)
292 .set(propFactory.create(ModeShapeLexicon.IS_HELD_BY_SESSION, value))
293 .on(pathFactory.create(locksPath, pathFactory.createSegment(lockToken)));
294 }
295
296
297
298
299
300
301
302
303
304 ModeShapeLock lockFor( String lockToken ) {
305 for (ModeShapeLock lock : workspaceLocksByNodeUuid.values()) {
306 if (lockToken.equals(lock.getLockToken())) {
307 return lock;
308 }
309 }
310
311 return null;
312 }
313
314
315
316
317
318
319
320
321 ModeShapeLock lockFor( JcrSession session,
322 Location nodeLocation ) {
323 UUID nodeUuid = uuidFor(session, nodeLocation);
324 if (nodeUuid == null) return null;
325 return workspaceLocksByNodeUuid.get(nodeUuid);
326 }
327
328
329
330
331
332
333
334
335
336
337 UUID uuidFor( JcrSession session,
338 Location location ) {
339 assert location != null;
340
341 if (location.getUuid() != null) return location.getUuid();
342
343 org.modeshape.graph.property.Property uuidProp = location.getIdProperty(JcrLexicon.UUID);
344 if (uuidProp == null) return null;
345
346 ExecutionContext context = session.getExecutionContext();
347 return context.getValueFactories().getUuidFactory().create(uuidProp.getFirstValue());
348 }
349
350
351
352
353
354
355 void cleanLocks( JcrSession session ) {
356 ExecutionContext context = session.getExecutionContext();
357 Collection<String> lockTokens = session.lockTokens();
358 for (String lockToken : lockTokens) {
359 ModeShapeLock lock = lockFor(lockToken);
360 if (lock != null && lock.isSessionScoped()) {
361 unlock(context, lock);
362 }
363 }
364 }
365
366
367
368
369
370 @ThreadSafe
371 public class ModeShapeLock {
372 final UUID nodeUuid;
373 private final UUID lockUuid;
374 private final String lockOwner;
375 private final boolean deep;
376 private final boolean sessionScoped;
377
378 @SuppressWarnings( "synthetic-access" )
379 ModeShapeLock( org.modeshape.graph.Node lockNode ) {
380 ValueFactory<String> stringFactory = context.getValueFactories().getStringFactory();
381 ValueFactory<UUID> uuidFactory = context.getValueFactories().getUuidFactory();
382 ValueFactory<Boolean> booleanFactory = context.getValueFactories().getBooleanFactory();
383
384 assert lockNode.getLocation().getPath() != null;
385
386 String lockUuidAsString = lockNode.getLocation().getPath().getLastSegment().getName().getLocalName();
387 Property lockOwnerProperty = lockNode.getProperty(JcrLexicon.LOCK_OWNER);
388 Property nodeUuidProperty = lockNode.getProperty(ModeShapeLexicon.LOCKED_UUID);
389 Property lockIsDeepProperty = lockNode.getProperty(JcrLexicon.LOCK_IS_DEEP);
390 Property isSessionScopedProperty = lockNode.getProperty(ModeShapeLexicon.IS_SESSION_SCOPED);
391
392 assert lockUuidAsString != null;
393 assert lockOwnerProperty != null;
394 assert nodeUuidProperty != null;
395 assert lockIsDeepProperty != null;
396 assert isSessionScopedProperty != null;
397
398 this.lockOwner = stringFactory.create(lockOwnerProperty.getFirstValue());
399 this.lockUuid = UUID.fromString(lockUuidAsString);
400 this.nodeUuid = uuidFactory.create(nodeUuidProperty.getFirstValue());
401 this.deep = booleanFactory.create(lockIsDeepProperty.getFirstValue());
402 this.sessionScoped = booleanFactory.create(isSessionScopedProperty.getFirstValue());
403 }
404
405 ModeShapeLock( String lockOwner,
406 UUID lockUuid,
407 UUID nodeUuid,
408 boolean deep,
409 boolean sessionScoped ) {
410 super();
411 this.lockOwner = lockOwner;
412 this.lockUuid = lockUuid;
413 this.nodeUuid = nodeUuid;
414 this.deep = deep;
415 this.sessionScoped = sessionScoped;
416 }
417
418 @SuppressWarnings( "synthetic-access" )
419 public boolean isLive() {
420 return workspaceLocksByNodeUuid.containsKey(nodeUuid);
421 }
422
423 public UUID getUuid() {
424 return lockUuid;
425 }
426
427 public boolean isDeep() {
428 return deep;
429 }
430
431 public String getLockOwner() {
432 return lockOwner;
433 }
434
435 public boolean isSessionScoped() {
436 return sessionScoped;
437 }
438
439 public String getLockToken() {
440 return lockUuid.toString();
441 }
442
443 @SuppressWarnings( "synthetic-access" )
444 public Lock lockFor( SessionCache cache ) throws RepositoryException {
445 final AbstractJcrNode node = cache.findJcrNode(Location.create(nodeUuid));
446 final JcrSession session = cache.session();
447 return new Lock() {
448 public String getLockOwner() {
449 return lockOwner;
450 }
451
452 public String getLockToken() {
453 String uuidString = lockUuid.toString();
454 return session.lockTokens().contains(uuidString) ? uuidString : null;
455 }
456
457 public Node getNode() {
458 return node;
459 }
460
461 public boolean isDeep() {
462 return deep;
463 }
464
465 public boolean isLive() {
466 return workspaceLocksByNodeUuid.containsKey(nodeUuid);
467 }
468
469 public boolean isSessionScoped() {
470 return sessionScoped;
471 }
472
473 public void refresh() throws LockException {
474 if (getLockToken() == null) {
475 throw new LockException(JcrI18n.notLocked.text(node.location));
476 }
477 }
478 };
479 }
480
481 }
482 }