View Javadoc

1   /**
2    * This file Copyright (c) 2013-2014 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
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   * Decorator to set appropriate Mgnl specific properties (e.g. mgnl:lastModification, mgnl:created).
80   *
81   * Caution: upon workspace clone the wrapper does not update the lastModification date in order to preserve contract of clone (make an identical copy).
82   */
83  public class MgnlPropertySettingContentDecorator extends PropertyAndChildWrappingContentDecorator implements ContentDecorator {
84  
85      /**
86       * inner holder of node info for all nodes that need their lud modified.
87       */
88      // as long as this class stays bound to session in which it is created, we don't need to store workspace name
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     // TODO: this behavior should be injectable w/ custom rules
118     /**
119      * Map below contains parent node type we try to resolve for LUD updates instead of the "current" path/node.
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      * Repository operation for update of mgnl:lastUpdated property.
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             // does it exist?
151             if (!sysSession.itemExists(destAbsPath)) {
152                 // can't do anything.
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             // might be null also after the failed attempt to resolve parent node that need to be marked as modified
163             if (node == null) {
164                 node = sysSession.getNode(destAbsPath);
165             }
166 
167             if (node.isNodeType(NodeTypes.MetaData.NAME)) {
168                 // no LUD on old style of metadata
169                 return;
170             }
171 
172             // skip root node
173             if (node.getDepth() == 0) {
174                 return;
175             }
176 
177             // unwrap
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                     // yeah, apparently that's what happens after non-yet-persisted in-session move
187                     if (!resolvedPath.equals(srcAbsPath) && !resolvedPath.equals(destAbsPath)) {
188                         // we are in real bind here ... what should be the dirty path?
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                 // trick or treat children
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             // save ... sucks, but since we are in system context there's no other way
214             // we are overriding save on session and call it on sys ctx as well instead of saving here
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                             // do not update LUD of acls
238                             continue;
239                         }
240                         if (node.isNodeType(NodeTypes.MetaData.NAME)) {
241                             // no LUD on old style of metadata
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      * Updates parent page or parent content mgnl:lastUpdated property on modification.
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      * Wrapper keeping last update date up to date.
366      *
367      * @deprecated since 5.2.2 - pls use MgnlPropertySettingWorkspaceWrapper instead.
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      * Updates destination parent page or parent content mgnl:lastUpdated property on move or copy operations.
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             // no update on clone. a) it doesn't work reliably (session refresh) and b) contract of clone is to make identical copy!
409         }
410 
411     }
412 
413     /**
414      * Wrapper keeping last update date up to date.
415      *
416      * @deprecated since 5.2.2 - pls use MgnlPropertySettingSessionWrapper instead.
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      * Updates all specific properties upon certain actions. Right now it's just updating the destination parent page or content mgnl:lastModified property on move.
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             // update src path, item is not moved yet
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                     // can happen when deleting something that was modified or moved within same session
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             // we do not maintain lud on root node
505             return;
506         }
507         // one date for all children
508         final Calendar updateDate = Calendar.getInstance();
509 
510         // DO ALL IN SYSTEM CONTEXT !!! USER MIGHT NOT HAVE ENOUGH RIGHTS
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         // does it exist?
538         if (!session.itemExists(absPath)) {
539             // can't do anything.
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         // don't do anything on null nodes
548         if (node == null) {
549             return;
550         }
551 
552         // skip root
553         if (node.getDepth() == 0) {
554             return;
555         }
556 
557         // unwrap if required
558         if (node instanceof DelegateNodeWrapper) {
559             node = ((DelegateNodeWrapper) node).deepUnwrap(MgnlPropertySettingNodeWrapper.class);
560         }
561 
562         final String user = MgnlContext.getUser().getName();
563 
564         // one date for all
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             // trick or treat children
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      * @return whether a modification to the property with the provided name should trigger an update of the last modification or not.
604      */
605     protected boolean shouldIgnoreUpdate(final String propertyName) {
606         // our update doesn't count as update (activation, versioning, last update, etc) - except for changing the template
607         return propertyName.startsWith(NodeTypes.JCR_PREFIX) || (propertyName.startsWith(NodeTypes.MGNL_PREFIX) && !NodeTypes.Renderable.TEMPLATE.equals(propertyName));
608     }
609 }