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