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