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.jcr.wrapper;
35
36 import info.magnolia.cms.security.JCRSessionOp;
37 import info.magnolia.context.MgnlContext;
38 import info.magnolia.jcr.decoration.ContentDecorator;
39 import info.magnolia.jcr.decoration.ContentDecoratorPropertyWrapper;
40 import info.magnolia.jcr.decoration.ContentDecoratorSessionWrapper;
41 import info.magnolia.jcr.decoration.ContentDecoratorWorkspaceWrapper;
42 import info.magnolia.jcr.util.NodeTypes;
43 import info.magnolia.jcr.util.NodeTypes.LastModified;
44 import info.magnolia.repository.RepositoryConstants;
45
46 import java.io.InputStream;
47 import java.math.BigDecimal;
48 import java.util.ArrayList;
49 import java.util.Calendar;
50 import java.util.LinkedList;
51 import java.util.List;
52 import java.util.Map;
53 import java.util.Stack;
54
55 import javax.jcr.AccessDeniedException;
56 import javax.jcr.Binary;
57 import javax.jcr.InvalidItemStateException;
58 import javax.jcr.ItemExistsException;
59 import javax.jcr.NoSuchWorkspaceException;
60 import javax.jcr.Node;
61 import javax.jcr.NodeIterator;
62 import javax.jcr.PathNotFoundException;
63 import javax.jcr.Property;
64 import javax.jcr.ReferentialIntegrityException;
65 import javax.jcr.RepositoryException;
66 import javax.jcr.Session;
67 import javax.jcr.Value;
68 import javax.jcr.ValueFormatException;
69 import javax.jcr.Workspace;
70 import javax.jcr.lock.LockException;
71 import javax.jcr.nodetype.ConstraintViolationException;
72 import javax.jcr.nodetype.NoSuchNodeTypeException;
73 import javax.jcr.version.VersionException;
74
75 import org.apache.commons.lang3.ArrayUtils;
76 import org.apache.commons.lang3.StringUtils;
77 import org.slf4j.Logger;
78 import org.slf4j.LoggerFactory;
79
80
81
82
83
84
85 public class MgnlPropertySettingContentDecorator extends PropertyAndChildWrappingContentDecorator implements ContentDecorator {
86
87
88
89
90
91 private class DirtyOp {
92
93 private String path;
94 private String userName;
95 private Calendar updateDate;
96
97 public DirtyOp(String path, String userName, Calendar updateDate) {
98 this.path = path;
99 this.userName = userName;
100 this.updateDate = updateDate;
101 }
102
103 public String getPath() {
104 return path;
105 }
106
107 public void setPath(String path) {
108 this.path = path;
109 }
110
111 public String getUserName() {
112 return userName;
113 }
114
115 public Calendar getUpdateDate() {
116 return updateDate;
117 }
118
119 }
120
121 private static final Logger log = LoggerFactory.getLogger(MgnlPropertySettingContentDecorator.class);
122
123
124
125
126
127 private static Map<String, String> parentNodeMappings = (Map) ArrayUtils.toMap(new String[][]{
128 {RepositoryConstants.WEBSITE, NodeTypes.Page.NAME},
129 {RepositoryConstants.USERS, NodeTypes.User.NAME}});
130
131 protected List<DirtyOp> dirtyOps = new LinkedList<DirtyOp>();
132
133
134
135
136
137 final class ChangeLastUpdateDateOp extends MgnlContext.RepositoryOp {
138 private final String workspaceName;
139 private final String userName;
140 private final String destAbsPath;
141 private final Calendar updateDate;
142 private final boolean recursiveDown;
143
144 ChangeLastUpdateDateOp(String workspaceName, String userName, String destAbsPath, Calendar updateDate, boolean recursiveDown) {
145 this.workspaceName = workspaceName;
146 this.userName = userName;
147 this.destAbsPath = destAbsPath;
148 this.updateDate = updateDate;
149 this.recursiveDown = recursiveDown;
150 }
151
152 @Override
153 public void doExec() throws RepositoryException {
154 Session sysSession = MgnlContext.getJCRSession(workspaceName);
155
156
157 if (!sysSession.itemExists(destAbsPath)) {
158
159 if (log.isDebugEnabled()) {
160 log.warn("Can't update mgnl:lastModified. Path {}:{} doesn't exist anymore.", workspaceName, destAbsPath);
161 }
162 return;
163 }
164 String srcAbsPath = sysSession.getNode(destAbsPath).getPath();
165
166 Stack<Node> nodes = resolveNodesToModify(workspaceName, destAbsPath, sysSession);
167 Node node;
168 if (!nodes.isEmpty()) {
169 node = nodes.pop();
170 } else {
171
172 node = sysSession.getNode(destAbsPath);
173 }
174
175 if (node.isNodeType(NodeTypes.MetaData.NAME)) {
176
177 return;
178 }
179
180
181 if (node.getDepth() == 0) {
182 return;
183 }
184
185
186 if (node instanceof DelegateNodeWrapper) {
187 node = ((DelegateNodeWrapper) node).deepUnwrap(MgnlPropertySettingNodeWrapper.class);
188 }
189
190 log.debug("LUD on {} from {}:{}", node.getPath(), node.getSession().toString(), Thread.currentThread().getName());
191 if (node.isNodeType(LastModified.NAME)) {
192 String resolvedPath = node.getPath();
193 if (!destAbsPath.equals(srcAbsPath)) {
194
195 if (!resolvedPath.equals(srcAbsPath) && !resolvedPath.equals(destAbsPath)) {
196
197 if (srcAbsPath.startsWith(resolvedPath)) {
198 String hunk = StringUtils.substringAfter(srcAbsPath, resolvedPath);
199 if (destAbsPath.endsWith(hunk)) {
200 resolvedPath = StringUtils.substringBefore(destAbsPath, hunk);
201 }
202 } else {
203 resolvedPath = destAbsPath;
204 }
205 }
206 }
207
208 dirtyOps.add(new DirtyOp(resolvedPath, userName, updateDate));
209 }
210
211
212 while (!nodes.isEmpty()) {
213 Node child = nodes.pop();
214 if (child.isNodeType(LastModified.NAME)) {
215 dirtyOps.add(new DirtyOp(child.getPath(), userName, updateDate));
216 }
217 }
218
219 if (recursiveDown) {
220
221 List<NodeIterator> iters = new ArrayList<NodeIterator>();
222 iters.add(node.getNodes());
223 while (!iters.isEmpty()) {
224 List<NodeIterator> tmp = updateChildren(node.getPath(), destAbsPath, iters, updateDate);
225 iters.clear();
226 iters.addAll(tmp);
227 }
228 }
229
230
231 return;
232 }
233
234
235
236
237
238
239
240
241
242
243
244
245
246 private Stack<Node> resolveNodesToModify(String workspace, String destAbsPath, Session sysSession) throws RepositoryException {
247 Stack<Node> nodeStack = new Stack<Node>();
248
249 String parentNodeType = parentNodeMappings.get(workspaceName);
250 Node node = sysSession.getNode(destAbsPath);
251 if (parentNodeType == null) {
252 return nodeStack;
253 }
254 while (node != null && !parentNodeType.equals(node.getPrimaryNodeType().getName()) && node.getDepth() > 0) {
255 nodeStack.add(node);
256 node = node.getParent();
257 }
258 nodeStack.add(node);
259 return nodeStack;
260 }
261
262 private List<NodeIterator> updateChildren(String srcAbsPath, String destAbsPath, List<NodeIterator> iters, Calendar updateDate) {
263 List<NodeIterator> tmp = new ArrayList<NodeIterator>();
264 for (NodeIterator iter : iters) {
265 while (iter.hasNext()) {
266 Node node = iter.nextNode();
267 try {
268 if (skipTypeInWorkspace(workspaceName, node.getPrimaryNodeType().getName())) {
269
270 continue;
271 }
272 if (node.isNodeType(NodeTypes.MetaData.NAME)) {
273
274 continue;
275 }
276 if (node.isNodeType(LastModified.NAME)) {
277 dirtyOps.add(new DirtyOp(destAbsPath + StringUtils.removeStart(node.getPath(), srcAbsPath), userName, updateDate));
278 }
279 tmp.add(node.getNodes());
280 } catch (RepositoryException e) {
281 log.error("Failed to update last modified date of {} with {}", node, e.getMessage(), e);
282 }
283 }
284 }
285 return tmp;
286 }
287
288 private boolean skipTypeInWorkspace(String workspaceName, String name) {
289 if (RepositoryConstants.USER_ROLES.equals(workspaceName)) {
290 return !(NodeTypes.Role.NAME.equals(name) || NodeTypes.Folder.NAME.equals(name));
291 }
292 if (RepositoryConstants.WEBSITE.equals(workspaceName)) {
293 return !NodeTypes.Page.NAME.equals(name);
294 }
295 return false;
296 }
297
298 }
299
300
301
302
303 public class LastUpdatePropertyWrapper extends ContentDecoratorPropertyWrapper<MgnlPropertySettingContentDecorator> implements Property {
304
305 public LastUpdatePropertyWrapper(Property property, MgnlPropertySettingContentDecorator contentDecorator) {
306 super(property, contentDecorator);
307 }
308
309 @Override
310 public void setValue(BigDecimal value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
311 super.setValue(value);
312 this.updateLastModifiedProperty();
313 }
314
315 @Override
316 public void setValue(Binary value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
317 super.setValue(value);
318 this.updateLastModifiedProperty();
319 }
320
321 @Override
322 public void setValue(boolean value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
323 super.setValue(value);
324 this.updateLastModifiedProperty();
325 }
326
327 @Override
328 public void setValue(Calendar value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
329 super.setValue(value);
330 this.updateLastModifiedProperty();
331 }
332
333 @Override
334 public void setValue(double value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
335 super.setValue(value);
336 this.updateLastModifiedProperty();
337 }
338
339 @Override
340 public void setValue(InputStream value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
341 super.setValue(value);
342 this.updateLastModifiedProperty();
343 }
344
345 @Override
346 public void setValue(long value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
347 super.setValue(value);
348 this.updateLastModifiedProperty();
349 }
350
351 @Override
352 public void setValue(Node value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
353 super.setValue(value);
354 this.updateLastModifiedProperty();
355 }
356
357 @Override
358 public void setValue(String value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
359 super.setValue(value);
360 this.updateLastModifiedProperty();
361 }
362
363 @Override
364 public void setValue(String[] values) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
365 super.setValue(values);
366 this.updateLastModifiedProperty();
367 }
368
369 @Override
370 public void setValue(Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
371 super.setValue(value);
372 this.updateLastModifiedProperty();
373 }
374
375 @Override
376 public void setValue(Value[] values) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
377 super.setValue(values);
378 this.updateLastModifiedProperty();
379 }
380
381 @Override
382 public void remove() throws VersionException, LockException, ConstraintViolationException, AccessDeniedException, RepositoryException {
383 Node parent = this.getParent();
384 String propertyName = this.getName();
385 super.remove();
386 if (shouldIgnoreUpdate(propertyName)) {
387 return;
388 }
389 updateLastModified(parent.getSession(), parent.getPath());
390 }
391
392 private void updateLastModifiedProperty() throws RepositoryException {
393 MgnlPropertySettingContentDecorator.this.updateLastModifiedProperty(getSession().getWorkspace().getName(), this.getName(), this.getParent().getPath());
394 }
395 }
396
397
398
399
400
401
402 public class LastUpdateWorkspaceWrapper extends MgnlPropertySettingWorkspaceWrapper {
403
404 protected LastUpdateWorkspaceWrapper(final Workspace workspace, final ContentDecorator contentDecorator) {
405 super(workspace, contentDecorator);
406 }
407 }
408
409
410
411
412 public class MgnlPropertySettingWorkspaceWrapper extends ContentDecoratorWorkspaceWrapper implements Workspace {
413
414 protected MgnlPropertySettingWorkspaceWrapper(final Workspace workspace, final ContentDecorator contentDecorator) {
415 super(workspace, contentDecorator);
416 }
417
418 @Override
419 public void move(String srcAbsPath, String destAbsPath) throws ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException {
420 super.move(srcAbsPath, destAbsPath);
421 updateLastModified(super.getWrappedWorkspace().getSession(), destAbsPath, true);
422 }
423
424 @Override
425 public void copy(String srcAbsPath, String destAbsPath) throws ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException {
426 super.copy(srcAbsPath, destAbsPath);
427 final String workspaceName = super.getWrappedWorkspace().getName();
428 setCreatedDate(workspaceName, destAbsPath, true);
429 }
430
431 @Override
432 public void copy(String srcWorkspace, String srcAbsPath, String destAbsPath) throws NoSuchWorkspaceException, ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException {
433 super.copy(srcWorkspace, srcAbsPath, destAbsPath);
434 final String workspaceName = super.getWrappedWorkspace().getName();
435 setCreatedDate(workspaceName, destAbsPath, true);
436 }
437
438 @Override
439 public void clone(String srcWorkspace, String srcAbsPath, String destAbsPath, boolean removeExisting) throws NoSuchWorkspaceException, ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException {
440 super.clone(srcWorkspace, srcAbsPath, destAbsPath, removeExisting);
441
442 }
443
444 }
445
446
447
448
449
450
451 public class LastUpdateSessionWrapper extends MgnlPropertySettingSessionWrapper {
452 public LastUpdateSessionWrapper(final Session session, final MgnlPropertySettingContentDecorator contentDecorator) {
453 super(session, contentDecorator);
454 }
455 }
456
457
458
459
460 public class MgnlPropertySettingSessionWrapper extends ContentDecoratorSessionWrapper<MgnlPropertySettingContentDecorator> implements Session {
461
462 public MgnlPropertySettingSessionWrapper(Session session, MgnlPropertySettingContentDecorator contentDecorator) {
463 super(session, contentDecorator);
464 }
465
466 @Override
467 public void move(final String srcAbsPath, final String destAbsPath) throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException, RepositoryException {
468 super.move(srcAbsPath, destAbsPath);
469
470 boolean onSourcePath = MgnlContext.doInSystemContext(new JCRSessionOp<Boolean>(super.getWrappedSession().getWorkspace().getName()) {
471 @Override
472 public Boolean exec(Session session) throws RepositoryException {
473 return session.itemExists(srcAbsPath);
474 }
475 });
476 String aPath = onSourcePath ? srcAbsPath : destAbsPath;
477 updateLastModified(super.getWrappedSession(), aPath, true);
478 if (onSourcePath) {
479 for (DirtyOp op : dirtyOps) {
480 if (op.getPath().equals(srcAbsPath) || op.getPath().startsWith(srcAbsPath + "/")) {
481 op.setPath(destAbsPath + StringUtils.substringAfter(op.getPath(), srcAbsPath));
482 }
483 }
484 }
485 }
486
487 @Override
488 public void save() throws AccessDeniedException, ItemExistsException, ReferentialIntegrityException, ConstraintViolationException, InvalidItemStateException, VersionException, LockException, NoSuchNodeTypeException, RepositoryException {
489 String workspaceName = wrapped.getWorkspace().getName();
490 try {
491 log.debug("saving session: {}::{}::sys:{}::{}", wrapped.toString(), workspaceName, MgnlContext.isSystemInstance(), Thread.currentThread().getName());
492 super.save();
493 } catch (InvalidItemStateException e) {
494 log.error("Failed to update LUD for session: {}::{}", wrapped.toString(), workspaceName, e);
495 throw e;
496 }
497 if (!dirtyOps.isEmpty()) {
498 Session sysSession = MgnlContext.getSystemContext().getJCRSession(workspaceName);
499 if (sysSession instanceof DelegateSessionWrapper) {
500 sysSession = ((DelegateSessionWrapper) sysSession).deepUnwrap(MgnlPropertySettingSessionWrapper.class);
501 }
502 applyPendingChanges(sysSession);
503 sysSession.save();
504 }
505 }
506
507 protected void applyPendingChanges(Session session) throws RepositoryException, PathNotFoundException {
508 while (!dirtyOps.isEmpty()) {
509 DirtyOp dirty = dirtyOps.remove(0);
510 if (session.nodeExists(dirty.getPath())) {
511 log.debug("Updating {} with {}", dirty.getPath(), dirty.getUpdateDate());
512 LastModified.update(session.getNode(dirty.getPath()), dirty.getUserName(), dirty.getUpdateDate());
513 } else {
514
515 if (log.isDebugEnabled()) {
516 log.warn("wanted to update {}:{} modified by:{} at {} but it's gone now.", session.getWorkspace().getName(), dirty.getPath(), dirty.getUserName(), dirty.getUpdateDate());
517 }
518 }
519 }
520 }
521
522 }
523
524 @Override
525 public Session wrapSession(Session session) {
526 return new MgnlPropertySettingSessionWrapper(session, this);
527 }
528
529 @Override
530 public Workspace wrapWorkspace(Workspace workspace) {
531 return new MgnlPropertySettingWorkspaceWrapper(workspace, this);
532 }
533
534 @Override
535 public Node wrapNode(Node node) {
536 return new MgnlPropertySettingNodeWrapper(node, this);
537 }
538
539 @Override
540 public Property wrapProperty(Property property) {
541 return new LastUpdatePropertyWrapper(property, this);
542 }
543
544 void updateLastModified(final Session session, final String destAbsPath, final boolean recursiveDown) throws RepositoryException, PathNotFoundException {
545 this.updateLastModified(session.getWorkspace().getName(), destAbsPath, recursiveDown);
546 }
547
548 void updateLastModified(final String workspaceName, final String destAbsPath, final boolean recursiveDown) throws RepositoryException, PathNotFoundException {
549
550 if ("/".equals(destAbsPath) && !recursiveDown) {
551
552 return;
553 }
554
555 final Calendar updateDate = Calendar.getInstance();
556
557
558 MgnlContext.doInSystemContext(new ChangeLastUpdateDateOp(workspaceName, MgnlContext.getUser().getName(), destAbsPath, updateDate, recursiveDown));
559
560 }
561
562 void updateLastModified(String workspaceName, String destAbsPath) throws RepositoryException, PathNotFoundException {
563 updateLastModified(workspaceName, destAbsPath, false);
564 }
565
566 void updateLastModified(Session session, String destAbsPath) throws RepositoryException, PathNotFoundException {
567 updateLastModified(session, destAbsPath, false);
568 }
569
570 void updateLastModifiedProperty(String workspaceName, String propertyName, String parentPath) throws RepositoryException {
571 if (shouldIgnoreUpdate(propertyName)) {
572 return;
573 }
574 updateLastModified(workspaceName, parentPath);
575 }
576
577 void setCreatedDate(final String workspaceName, final String absPath) throws RepositoryException {
578 setCreatedDate(workspaceName, absPath, false);
579 }
580
581 void setCreatedDate(final String workspaceName, final String absPath, final boolean recursiveDown) throws RepositoryException {
582 final Session session = MgnlContext.getJCRSession(workspaceName);
583
584
585 if (!session.itemExists(absPath)) {
586
587 if (log.isDebugEnabled()) {
588 log.warn("Can't update {}. Path {}:{} does not exist.", NodeTypes.Created.NAME, workspaceName, absPath);
589 }
590 return;
591 }
592 Node node = session.getNode(absPath);
593
594
595 if (node == null) {
596 return;
597 }
598
599
600 if (node.getDepth() == 0) {
601 return;
602 }
603
604
605 if (node instanceof DelegateNodeWrapper) {
606 node = ((DelegateNodeWrapper) node).deepUnwrap(MgnlPropertySettingNodeWrapper.class);
607 }
608
609 final String user = MgnlContext.getUser().getName();
610
611
612 final Calendar now = Calendar.getInstance();
613
614 if (node.isNodeType(NodeTypes.Created.NAME)) {
615 log.debug("Setting {} on {} from {}:{}", NodeTypes.Created.NAME, node.getPath(), node.getSession().toString(), Thread.currentThread().getName());
616 NodeTypes.Created.set(node, user, now);
617 }
618
619 if (recursiveDown) {
620
621 List<NodeIterator> iters = new ArrayList<NodeIterator>();
622 iters.add(node.getNodes());
623 while (!iters.isEmpty()) {
624 List<NodeIterator> tmp = updateChildren(iters, user, now);
625 iters.clear();
626 iters.addAll(tmp);
627 }
628 }
629 }
630
631 private List<NodeIterator> updateChildren(final List<NodeIterator> iters, final String user, final Calendar updateDate) {
632 List<NodeIterator> tmp = new ArrayList<NodeIterator>();
633 for (NodeIterator iterator : iters) {
634 while (iterator.hasNext()) {
635 Node node = iterator.nextNode();
636 try {
637 if (node.isNodeType(NodeTypes.Created.NAME)) {
638 NodeTypes.Created.set(node, user, updateDate);
639 }
640 tmp.add(node.getNodes());
641 } catch (RepositoryException e) {
642 log.error("Failed to set created date of {} with {}", node, e.getMessage(), e);
643 }
644 }
645 }
646 return tmp;
647 }
648
649
650
651
652 protected boolean shouldIgnoreUpdate(final String propertyName) {
653
654 return propertyName.startsWith(NodeTypes.JCR_PREFIX) || (propertyName.startsWith(NodeTypes.MGNL_PREFIX) && !NodeTypes.Renderable.TEMPLATE.equals(propertyName));
655 }
656 }