1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.context;
35
36 import info.magnolia.cms.beans.runtime.MultipartForm;
37 import info.magnolia.cms.core.AggregationState;
38 import info.magnolia.cms.core.HierarchyManager;
39 import info.magnolia.cms.core.search.QueryManager;
40 import info.magnolia.cms.i18n.Messages;
41 import info.magnolia.cms.security.AccessManager;
42 import info.magnolia.cms.security.User;
43
44 import java.util.Locale;
45 import java.util.Map;
46
47 import javax.jcr.InvalidItemStateException;
48 import javax.jcr.LoginException;
49 import javax.jcr.Node;
50 import javax.jcr.RepositoryException;
51 import javax.jcr.Session;
52 import javax.jcr.lock.LockException;
53 import javax.jcr.lock.LockManager;
54 import javax.security.auth.Subject;
55 import javax.servlet.ServletContext;
56 import javax.servlet.http.HttpServletRequest;
57 import javax.servlet.http.HttpServletResponse;
58
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61
62
63
64
65
66
67
68
69
70
71
72 public class MgnlContext {
73 private static final Logger log = LoggerFactory.getLogger(MgnlContext.class);
74
75
76
77
78 private static ThreadLocal<Context> localContext = new ThreadLocal<Context>();
79
80
81
82
83 public MgnlContext() {
84 }
85
86
87
88
89
90
91 public static User getUser() {
92 return getInstance().getUser();
93 }
94
95
96
97
98 public static void setLocale(Locale locale) {
99 getInstance().setLocale(locale);
100 }
101
102
103
104
105
106
107 public static Locale getLocale() {
108 return getInstance().getLocale();
109 }
110
111 public static Messages getMessages() {
112 return getInstance().getMessages();
113 }
114
115 public static Messages getMessages(String basename) {
116 return getInstance().getMessages(basename);
117 }
118
119 public static void login(Subject subject) {
120 ((UserContext) getInstance()).login(subject);
121 }
122
123
124
125
126
127
128 @Deprecated
129 public static HierarchyManager getHierarchyManager(String repositoryId) {
130 return getInstance().getHierarchyManager(repositoryId);
131 }
132
133
134
135
136 public static AccessManager getAccessManager(String name) {
137 return getInstance().getAccessManager(name);
138 }
139
140
141
142
143
144
145 @Deprecated
146 public static QueryManager getQueryManager(String workspaceName) {
147 return getInstance().getQueryManager(workspaceName);
148 }
149
150
151
152
153
154
155
156 public static MultipartForm getPostedForm() {
157 WebContext ctx = getWebContextOrNull();
158 if (ctx != null) {
159 return ctx.getPostedForm();
160 }
161 return null;
162 }
163
164
165
166
167 public static String getParameter(String name) {
168 WebContext ctx = getWebContextOrNull();
169 if (ctx != null) {
170 return ctx.getParameter(name);
171 }
172 return null;
173
174 }
175
176 public static String[] getParameterValues(String name) {
177 WebContext ctx = getWebContextOrNull();
178 if (ctx != null) {
179 return ctx.getParameterValues(name);
180 }
181 return null;
182
183 }
184
185
186
187
188 public static Map<String, String> getParameters() {
189 WebContext ctx = getWebContextOrNull();
190 if (ctx != null) {
191 return ctx.getParameters();
192 }
193 return null;
194 }
195
196
197
198
199 public static String getContextPath() {
200 WebContext ctx = getWebContextOrNull();
201 if (ctx != null) {
202 return ctx.getContextPath();
203 }
204 throw new IllegalStateException("Can only get the context path within a WebContext.");
205 }
206
207
208
209
210
211 public static AggregationState getAggregationState() {
212 final WebContext ctx = getWebContextOrNull();
213 if (ctx != null) {
214 return ctx.getAggregationState();
215 }
216 throw new IllegalStateException("Can only get the aggregation state within a WebContext.");
217 }
218
219
220
221
222 public static void resetAggregationState() {
223 final WebContext ctx = getWebContextOrNull();
224 if (ctx != null) {
225 ctx.resetAggregationState();
226 } else {
227 throw new IllegalStateException("Can only reset the aggregation state within a WebContext.");
228 }
229 }
230
231
232
233
234 public static void setAttribute(String name, Object value) {
235 getInstance().setAttribute(name, value, Context.LOCAL_SCOPE);
236 }
237
238
239
240
241
242
243 public static void setAttribute(String name, Object value, int scope) {
244 getInstance().setAttribute(name, value, scope);
245 }
246
247
248
249
250 public static <T> T getAttribute(String name) {
251 return (T) getInstance().getAttribute(name);
252 }
253
254
255
256
257 public static <T> T getAttribute(String name, int scope) {
258 return (T) getInstance().getAttribute(name, scope);
259 }
260
261
262
263
264 public static boolean hasAttribute(String name) {
265 return getInstance().getAttribute(name, Context.LOCAL_SCOPE) != null;
266 }
267
268
269
270
271 public static boolean hasAttribute(String name, int scope) {
272 return getInstance().getAttribute(name, scope) != null;
273 }
274
275
276
277
278 public static void removeAttribute(String name) {
279 getInstance().removeAttribute(name, Context.LOCAL_SCOPE);
280 }
281
282
283
284
285 public static void removeAttribute(String name, int scope) {
286 getInstance().removeAttribute(name, scope);
287 }
288
289
290
291
292 public static void setInstance(Context context) {
293 localContext.set(context);
294 }
295
296
297
298
299 public static Context getInstance() {
300 Context context = localContext.get();
301
302 if (context == null) {
303 final IllegalStateException ise = new IllegalStateException("MgnlContext is not set for this thread");
304 log.error("MgnlContext is not initialized. This could happen if the request does not go through the Magnolia default filters.", ise);
305 throw ise;
306 }
307 return context;
308 }
309
310
311
312
313
314
315 public static WebContext getWebContext() {
316 return getWebContext(null);
317 }
318
319
320
321
322
323
324
325
326 public static WebContext getWebContext(String exceptionMessage) {
327 final WebContext wc = getWebContextIfExisting(getInstance());
328 if (wc == null) {
329 throw new IllegalStateException(exceptionMessage == null ? "The current context is not an instance of WebContext (" + localContext.get() + ")" : exceptionMessage);
330 }
331 return wc;
332 }
333
334
335
336
337 public static WebContext getWebContextOrNull() {
338 return hasInstance() ? getWebContextIfExisting(getInstance()) : null;
339 }
340
341
342
343
344
345
346 public static boolean hasInstance() {
347 return localContext.get() != null;
348 }
349
350 public static boolean isSystemInstance() {
351 return localContext.get() instanceof SystemContext;
352 }
353
354
355
356
357 public static boolean isWebContext() {
358 return hasInstance() && getWebContextIfExisting(getInstance()) != null;
359 }
360
361
362
363
364
365
366 @Deprecated
367 public static SystemContext getSystemContext() {
368 return ContextFactory.getInstance().getSystemContext();
369 }
370
371
372
373
374
375
376 public static <T, E extends Throwable> T doInSystemContext(final Op<T, E> op) throws E {
377 return doInSystemContext(op, false);
378 }
379
380
381
382
383
384
385
386
387 public static <T, E extends Throwable> T doInSystemContext(final Op<T, E> op, boolean releaseAfterExecution) throws E {
388 final Context originalCtx = MgnlContext.hasInstance() ? MgnlContext.getInstance() : null;
389 T result;
390 try {
391 MgnlContext.setInstance(MgnlContext.getSystemContext());
392 result = op.exec();
393 } finally {
394 if (releaseAfterExecution) {
395 MgnlContext.release();
396 }
397 MgnlContext.setInstance(originalCtx);
398 }
399 return result;
400 }
401
402
403
404
405
406
407
408
409
410
411 public static interface Op<T, E extends Throwable> {
412 T exec() throws E;
413 }
414
415
416
417
418 public abstract static class VoidOp implements Op<Void, RuntimeException> {
419 @Override
420 public Void exec() {
421 doExec();
422 return null;
423 }
424
425 abstract public void doExec();
426 }
427
428
429
430
431 public abstract static class RepositoryOp implements Op<Void, RepositoryException> {
432 @Override
433 public Void exec() throws RepositoryException {
434 doExec();
435 return null;
436 }
437
438 abstract public void doExec() throws RepositoryException;
439 }
440
441
442
443
444 public abstract static class LockingOp extends RepositoryOp {
445
446 private final long sleepTime = 200;
447 private final int maxAttempts = 50;
448
449 final private String workspaceName;
450 final private String lockPath;
451 final private String userName;
452 final private boolean deepLock;
453 final private String lockedNodeType;
454
455
456
457
458 public LockingOp(String workspaceName, String lockPath) {
459 this(workspaceName, lockPath, false);
460 }
461
462
463
464
465
466 public LockingOp(String workspaceName, String lockPath, boolean deepLock) {
467 this(workspaceName, lockPath, deepLock, null);
468 }
469
470
471
472
473
474 public LockingOp(String workspaceName, String lockPath, String nodeType) {
475
476 this(workspaceName, lockPath, false, nodeType, MgnlContext.getUser() == null ? "not available" : MgnlContext.getUser().getName());
477 }
478
479
480
481
482
483
484 public LockingOp(String workspaceName, String lockPath, boolean deepLock, String nodeType) {
485
486 this(workspaceName, lockPath, deepLock, nodeType, MgnlContext.getUser() == null ? "not available" : MgnlContext.getUser().getName());
487 }
488
489
490
491
492
493
494
495 public LockingOp(String workspaceName, String lockPath, boolean deepLock, String lockedNodeType, String userName) {
496 this.workspaceName = workspaceName;
497 this.userName = userName;
498 this.deepLock = deepLock;
499 this.lockedNodeType = lockedNodeType;
500 this.lockPath = lockPath;
501 }
502
503 private String getLockPath() throws RepositoryException {
504 if (lockedNodeType == null) {
505 return lockPath;
506 }
507 Session sysSession = MgnlContext.getJCRSession(workspaceName);
508 Node parentNode = sysSession.getNode(lockPath);
509 Node lockNode = parentNode;
510 while (lockNode != null && lockNode.getDepth() > 0 && !lockedNodeType.equals(lockNode.getPrimaryNodeType().getName())) {
511 lockNode = lockNode.getParent();
512 }
513
514 final String lockPath;
515 if (lockNode == null || lockNode.getDepth() == 0) {
516 if (parentNode == null) {
517 throw new RepositoryException("Can't perform locking operaion without the locked node. " + workspaceName + ":" + this.lockPath + " doesn't exist.");
518 }
519
520 lockPath = parentNode.getPath();
521 log.info("Failed to find page path for {}:{}", workspaceName, lockPath);
522 } else {
523 lockPath = lockNode.getPath();
524 }
525 return lockPath;
526 }
527
528 @Override
529 public Void exec() throws RepositoryException {
530 final String threadName = Thread.currentThread().getName();
531 final String lockPath = getLockPath();
532 final Session sysSession = MgnlContext.getJCRSession(workspaceName);
533 final String sessionName = sysSession.toString();
534 final LockManager guard = sysSession.getWorkspace().getLockManager();
535
536 int attempts = maxAttempts;
537 while (attempts > 0) {
538 if (!guard.isLocked(lockPath)) {
539 try {
540 Node node = sysSession.getNode(lockPath);
541 if (!node.isNodeType("mix:lockable")) {
542 node.addMixin("mix:lockable");
543 }
544 node.save();
545 guard.lock(lockPath, deepLock, true, sleepTime * maxAttempts, "Lock guarded node update requested by " + userName);
546 log.debug("Locked {} from {}:{}", lockPath, sessionName, threadName);
547 break;
548 } catch (LockException e) {
549 log.debug("this should happen very rarely. If you are seeing this message something is probably very wrong.", e);
550
551 }
552 }
553 try {
554 Thread.sleep(sleepTime);
555 } catch (InterruptedException e) {
556 Thread.interrupted();
557 }
558 attempts--;
559 }
560 if (attempts == 0) {
561 log.info("Lock on {} is already held by {}", lockPath, guard.getLock(lockPath));
562
563 String message = "Failed to obtain lock by " + userName + "(" + threadName + ") on " + workspaceName + ":" + lockPath + " within " + ((maxAttempts * sleepTime) / 1000) + " seconds. Will NOT execute operation that requires locking.";
564 throw new LockException(message);
565 }
566 long timestamp = System.nanoTime();
567 try {
568 doExec();
569 } catch (LockException e) {
570 String failedPath = e.getFailureNodePath();
571
572 log.error("Lock exception while updating node [{}] supposedly guarded by lock. is it really locked? {}, we hold the lock? {} ... as for [{}] is it locked? {}, we hold the lock? {}", failedPath, "" + guard.isLocked(failedPath), "" + guard.holdsLock(failedPath), lockPath, "" + guard.isLocked(lockPath), "" + guard.holdsLock(lockPath));
573
574 } finally {
575 timestamp = System.nanoTime() - timestamp;
576
577 if (timestamp > 2000000000L) {
578 log.warn("Lock guarded operation on {}:{}:{} performed by {}:{} took {} seconds to execute. Performance of your server might be sub-optimal.", sessionName, workspaceName, lockPath, userName, threadName, "" + (timestamp / 1000000000L));
579 }
580
581 log.debug("Unocking {} from {}:{}", lockPath, sessionName, threadName);
582 try {
583 guard.unlock(lockPath);
584 } catch (InvalidItemStateException e) {
585 log.error("Failed to unlock {} from {}:{} with {}. Will attempt to save the session and try again.", lockPath, sessionName, threadName, e.getMessage(), e);
586 sysSession.save();
587 guard.unlock(lockPath);
588 }
589 }
590 return null;
591 }
592 }
593
594
595
596
597
598
599
600
601
602 @Deprecated
603 public static void initAsWebContext(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) {
604 WebContext ctx = ContextFactory.getInstance().createWebContext(request, response, servletContext);
605 setInstance(ctx);
606 }
607
608
609
610
611
612
613 private static WebContext getWebContextIfExisting(Context ctx) {
614 if (ctx instanceof WebContext) {
615 return (WebContext) ctx;
616 } else if (ctx instanceof ContextDecorator) {
617 return getWebContextIfExisting(((ContextDecorator) ctx).getWrappedContext());
618 }
619 return null;
620 }
621
622
623
624
625 public static void release() {
626 if (hasInstance() && !(getInstance() instanceof SystemContext)) {
627 getInstance().release();
628 }
629 SystemContext systemContext = getSystemContext();
630 if (systemContext instanceof ThreadDependentSystemContext) {
631 ((ThreadDependentSystemContext) systemContext).releaseThread();
632 }
633 }
634
635 public static void push(HttpServletRequest request, HttpServletResponse response) {
636 if (isWebContext()) {
637 WebContext wc = getWebContext();
638 wc.push(request, response);
639 }
640 }
641
642 public static void pop() {
643 if (isWebContext()) {
644 WebContext wc = getWebContext();
645 wc.pop();
646 }
647 }
648
649
650
651
652
653
654
655 public static Session getJCRSession(String workspaceName) throws LoginException, RepositoryException {
656 return getInstance().getJCRSession(workspaceName);
657 }
658
659 public static Subject getSubject() {
660 return getInstance().getSubject();
661 }
662 }