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