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