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