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