View Javadoc
1   /**
2    * This file Copyright (c) 2012-2018 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.util;
35  
36  import info.magnolia.context.Context;
37  import info.magnolia.context.MgnlContext;
38  import info.magnolia.context.SystemContext;
39  
40  import java.util.Calendar;
41  
42  import javax.jcr.Node;
43  import javax.jcr.RepositoryException;
44  import javax.jcr.version.Version;
45  
46  import org.slf4j.Logger;
47  import org.slf4j.LoggerFactory;
48  
49  /**
50   * Magnolia defined NodeTypes together with their properties and some convenience methods.
51   */
52  public class NodeTypes {
53  
54      private static final Logger log = LoggerFactory.getLogger(NodeTypes.class);
55  
56      /**
57       * Namespace for Magnolia extensions.
58       */
59      public static final String MGNL_PREFIX = "mgnl:";
60  
61      /**
62       * Namespace for jcr nodes and properties.
63       */
64      public static final String JCR_PREFIX = "jcr:";
65  
66      /**
67       * Namespace for rep nodes and properties.
68       */
69      public static final String REP_PREFIX = "rep:";
70  
71      /**
72       * Default suffix for userName keeping properties.
73       */
74      private static final String BY = "By";
75  
76      /**
77       * Represents the mixin mgnl:lastModified.
78       */
79      public static class LastModified {
80          public static final String NAME = MGNL_PREFIX + "lastModified";
81          public static final String LAST_MODIFIED = NAME;
82          public static final String LAST_MODIFIED_BY = LAST_MODIFIED + BY;
83  
84          /**
85           * Returns the date when this node was last modified. If the no modification date has been stored on the node this
86           * method return the creation date if set, otherwise null is returned.
87           */
88          public static Calendar getLastModified(Node node) throws RepositoryException {
89              return node.hasProperty(LAST_MODIFIED) ? node.getProperty(LAST_MODIFIED).getDate() : Created.getCreated(node);
90          }
91  
92          /**
93           * Returns the name of the user that last modified the node. If no modification has been stored on the node
94           * this method return the name of the user that created the node if set, otherwise null is returned.
95           */
96          public static String getLastModifiedBy(Node node) throws RepositoryException {
97              return node.hasProperty(LAST_MODIFIED_BY) ? node.getProperty(LAST_MODIFIED_BY).getString() : Created.getCreatedBy(node);
98          }
99  
100         /**
101          * Sets the date of modification to current Calendar and uses {@link info.magnolia.context.MgnlContext} to set the name of the user.
102          * It should not be necessary to call this method explicitly as all Magnolia treated node instances are self updating the property defined by mixin.
103          *
104          * @see info.magnolia.jcr.wrapper.MgnlPropertySettingContentDecorator for more details.
105          */
106         public static void update(Node node) throws RepositoryException {
107             update(node, getCurrentCalendar());
108         }
109 
110         /**
111          * Sets the date of modification.
112          * It should not be necessary to call this method explicitly as all Magnolia treated node instances are self updating the property defined by mixin.
113          *
114          * @see info.magnolia.jcr.wrapper.MgnlPropertySettingContentDecorator for more details.
115          */
116         public static void update(Node node, Calendar lastModified) throws RepositoryException {
117             update(node, getCurrentUserName(), lastModified);
118         }
119 
120         /**
121          * Sets the date of modification and the name of the user modifying a node.
122          * It should not be necessary to call this method explicitly as all Magnolia treated node instances are self updating the property defined by mixin.
123          *
124          * @see info.magnolia.jcr.wrapper.MgnlPropertySettingContentDecorator for more details.
125          */
126         public static void update(Node node, String userName, Calendar lastModified) throws RepositoryException {
127             checkNodeType(node, LastModified.NAME, LAST_MODIFIED, LAST_MODIFIED_BY);
128             node.setProperty(LAST_MODIFIED, lastModified);
129             node.setProperty(LAST_MODIFIED_BY, userName);
130         }
131 
132     }
133 
134     /**
135      * Represents the mixin mgnl:activatable.
136      */
137     public static class Activatable {
138         public static final String NAME = MGNL_PREFIX + "activatable";
139         public static final String LAST_ACTIVATED = MGNL_PREFIX + "lastActivated";
140         public static final String LAST_ACTIVATED_BY = LAST_ACTIVATED + BY;
141         public static final String LAST_ACTIVATED_VERSION = LAST_ACTIVATED + "Version";
142         public static final String LAST_ACTIVATED_VERSION_CREATED = LAST_ACTIVATED_VERSION + "Created";
143         public static final String ACTIVATION_STATUS = MGNL_PREFIX + "activationStatus";
144 
145         public static final int ACTIVATION_STATUS_NOT_ACTIVATED = 0;
146 
147         public static final int ACTIVATION_STATUS_MODIFIED = 1;
148 
149         public static final int ACTIVATION_STATUS_ACTIVATED = 2;
150 
151         /**
152          * Returns the activation status of the node. Returns one of the constants:
153          * <ul>
154          * <li>{@link #ACTIVATION_STATUS_NOT_ACTIVATED} if the node has not been activated</li>
155          * <li>{@link #ACTIVATION_STATUS_MODIFIED} has been activated and subsequently modified</li>
156          * <li>{@link #ACTIVATION_STATUS_ACTIVATED} has been activated and not modified since</li>
157          * </ul>
158          */
159         public static int getActivationStatus(Node node) throws RepositoryException {
160 
161             if (!isActivated(node)) {
162                 // never activated or deactivated
163                 return ACTIVATION_STATUS_NOT_ACTIVATED;
164             }
165 
166             Calendar lastModified = LastModified.getLastModified(node);
167             Calendar lastActivatedVersionCreated = null;
168 
169             if (!NodeUtil.isNodeType(node, Versionable.NAME)) {
170                 Node versionableNode = NodeUtil.getNearestAncestorOfType(node, Versionable.NAME);
171                 if (versionableNode != null) {
172                     lastActivatedVersionCreated = getLastActivatedVersionCreated(versionableNode);
173                 }
174             } else {
175                 lastActivatedVersionCreated = getLastActivatedVersionCreated(node);
176             }
177 
178             Calendar lastActivated = lastActivatedVersionCreated != null ? lastActivatedVersionCreated : getLastActivated(node);
179 
180             if (lastModified != null && lastModified.after(lastActivated)) {
181                 // node has been modified after last activation
182                 return ACTIVATION_STATUS_MODIFIED;
183             }
184 
185             // activated and not modified ever since
186             return ACTIVATION_STATUS_ACTIVATED;
187         }
188 
189         /**
190          * Returns true if the node has been activated.
191          */
192         public static boolean isActivated(Node node) throws RepositoryException {
193             return node.hasProperty(ACTIVATION_STATUS) && node.getProperty(ACTIVATION_STATUS).getBoolean();
194         }
195 
196         /**
197          * Returns the date when the node was last activated or null if no activation date has been stored on the node.
198          */
199         public static Calendar getLastActivated(Node node) throws RepositoryException {
200             return node.hasProperty(LAST_ACTIVATED) ? node.getProperty(LAST_ACTIVATED).getDate() : null;
201         }
202 
203         /**
204          * Returns the name of the user that last activated the node or null if no activating user has been stored on the node.
205          */
206         public static String getLastActivatedBy(Node node) throws RepositoryException {
207             return node.hasProperty(LAST_ACTIVATED_BY) ? node.getProperty(LAST_ACTIVATED_BY).getString() : null;
208         }
209 
210         /**
211          * Returns the latest activated version name or null if the version name isn't set.
212          */
213         public static String getLastActivatedVersion(Node node) throws RepositoryException {
214             return node.hasProperty(LAST_ACTIVATED_VERSION) ? node.getProperty(LAST_ACTIVATED_VERSION).getString() : null;
215         }
216 
217         /**
218          * Returns the latest activated version creation time or null if the version creation time isn't set.
219          */
220         public static Calendar getLastActivatedVersionCreated(Node node) throws RepositoryException {
221             return node.hasProperty(LAST_ACTIVATED_VERSION_CREATED) ? node.getProperty(LAST_ACTIVATED_VERSION_CREATED).getDate() : null;
222         }
223 
224         /**
225          * Sets the name of the user that performed the most recent activation as well as to current time.
226          */
227         public static void update(Node node, String userName, boolean isActivated) throws RepositoryException {
228             update(node, userName, isActivated, (Version) null);
229         }
230 
231         /**
232          * Sets the name of the user that performed the most recent activation as well as to current time and last activated version name.
233          * @deprecated since 5.6.2, use {@link #update(Node, String, boolean, Version)}
234          */
235         @Deprecated
236         public static void update(Node node, String userName, boolean isActivated, String versionName) throws RepositoryException {
237             node.setProperty(LAST_ACTIVATED_VERSION, versionName);
238             update(node, userName, isActivated, (Version) null);
239         }
240 
241         /**
242          * Sets the name of the user that performed the most recent activation as well as to current time, last activated version name and last activated version creation time.
243          */
244         public static void update(Node node, String userName, boolean isActivated, Version version) throws RepositoryException {
245             checkNodeType(node, Activatable.NAME, LAST_ACTIVATED, LAST_ACTIVATED_BY, ACTIVATION_STATUS, LAST_ACTIVATED_VERSION);
246             node.setProperty(LAST_ACTIVATED, getCurrentCalendar());
247             node.setProperty(LAST_ACTIVATED_BY, userName);
248             node.setProperty(ACTIVATION_STATUS, isActivated);
249             if (version != null) {
250                 node.setProperty(LAST_ACTIVATED_VERSION, version.getName());
251                 node.setProperty(LAST_ACTIVATED_VERSION_CREATED, version.getCreated());
252             }
253         }
254 
255     }
256 
257     /**
258      * Represents the mixin mgnl:created.
259      */
260     public static class Created {
261         public static final String NAME = MGNL_PREFIX + "created";
262         public static final String CREATED = NAME;
263         public static final String CREATED_BY = CREATED + BY;
264 
265         /**
266          * Returns the creation date of a node or null if creation date isn't set.
267          */
268         public static Calendar getCreated(Node node) throws RepositoryException {
269             return node.hasProperty(CREATED) ? node.getProperty(CREATED).getDate() : null;
270         }
271 
272         /**
273          * Returns the name of the user that created a node.
274          */
275         public static String getCreatedBy(Node node) throws RepositoryException {
276             return node.hasProperty(CREATED_BY) ? node.getProperty(CREATED_BY).getString() : null;
277         }
278 
279         /**
280          * Sets the current date as the node's creation date and uses {@link info.magnolia.context.MgnlContext} to set the name of the creating
281          * user. Used with nodes having the <code>mgnl:created</code> mixin.
282          */
283         public static void set(Node node) throws RepositoryException {
284             set(node, getCurrentUserName(), getCurrentCalendar());
285         }
286 
287         /**
288          * Sets the supplied date as the node's creation date and sets the name of the creating user. Also sets the date of
289          * modification and the user last having modified the node to the same values. Used with nodes having the
290          * <code>mgnl:created</code> mixin.
291          */
292         public static void set(Node node, String userName, Calendar created) throws RepositoryException {
293             checkNodeType(node, NAME, CREATED, CREATED_BY);
294             node.setProperty(CREATED, created);
295             node.setProperty(CREATED_BY, userName);
296 
297             LastModified.update(node, userName, created);
298         }
299     }
300 
301     /**
302      * Represents the mixin mgnl:renderable.
303      */
304     public static class Renderable {
305         public static final String NAME = MGNL_PREFIX + "renderable";
306         public static final String TEMPLATE = MGNL_PREFIX + "template";
307 
308         /**
309          * Returns the template assigned to the node or null of none has been assigned. Used with nodes having the
310          * <code>mgnl:renderable</code> mixin.
311          */
312         public static String getTemplate(Node node) throws RepositoryException {
313             return node.hasProperty(TEMPLATE) ? node.getProperty(TEMPLATE).getString() : null;
314         }
315 
316         /**
317          * Sets the template assigned to the node. Used with nodes having the <code>mgnl:renderable</code> mixin.
318          */
319         public static void set(Node node, String template) throws RepositoryException {
320             checkNodeType(node, NAME, TEMPLATE);
321             node.setProperty(TEMPLATE, template);
322         }
323     }
324 
325     /**
326      * Represents the mixin mgnl:deleted.
327      */
328     public static class Deleted {
329         public static final String NAME = MGNL_PREFIX + "deleted";
330         public static final String DELETED = NAME;
331         public static final String DELETED_BY = DELETED + BY;
332         public static final String COMMENT = MGNL_PREFIX + "comment";
333 
334         /**
335          * Returns the date when the node was deleted or null if no deletion date has been stored on the node.
336          */
337         public static Calendar getDeleted(Node node) throws RepositoryException {
338             return node.hasProperty(DELETED) ? node.getProperty(DELETED).getDate() : null;
339         }
340 
341         /**
342          * Returns the name of the user that deleted the node or null if no deleting user has been stored on the node.
343          */
344         public static String getDeletedBy(Node node) throws RepositoryException {
345             return node.hasProperty(DELETED_BY) ? node.getProperty(DELETED_BY).getString() : null;
346         }
347 
348         /**
349          * Returns the comment set when then node was last deleted or null if no comment has been set.
350          */
351         public static String getComment(Node node) throws RepositoryException {
352             return node.hasProperty(COMMENT) ? node.getProperty(COMMENT).getString() : null;
353         }
354 
355         public static void set(Node node, String comment) throws RepositoryException {
356             checkNodeType(node, NAME, DELETED, DELETED_BY, COMMENT);
357             node.setProperty(DELETED, getCurrentCalendar());
358             node.setProperty(DELETED_BY, getCurrentUserName());
359             node.setProperty(COMMENT, comment);
360         }
361     }
362 
363     /**
364      * Represents the mixin mgnl:versionable.
365      */
366     public static class Versionable {
367         public static final String NAME = MGNL_PREFIX + "versionable";
368         public static final String COMMENT = Deleted.COMMENT;
369 
370         /**
371          * Returns the comment set when then node was last versioned or null if no comment has been set.
372          */
373         public static String getComment(Node node) throws RepositoryException {
374             return node.hasProperty(COMMENT) ? node.getProperty(COMMENT).getString() : null;
375         }
376 
377         /**
378          * Set the versioning comment on the node.
379          */
380         public static void set(Node node, String comment) throws RepositoryException {
381             checkNodeType(node, NAME, COMMENT);
382             node.setProperty(COMMENT, comment);
383         }
384     }
385 
386     /**
387      * Represents the nodeType mgnl:folder.
388      */
389     public static class Folder {
390         public static final String NAME = MGNL_PREFIX + "folder";
391     }
392 
393     /**
394      * Represents the nodeType mgnl:resource.
395      */
396     public static class Resource {
397         public static final String NAME = MGNL_PREFIX + "resource";
398     }
399 
400     /**
401      * Represents the nodeType mgnl:content.
402      */
403     public static class Content {
404         public static final String NAME = MGNL_PREFIX + "content";
405     }
406 
407     /**
408      * Represents the nodeType mgnl:contentNode.
409      */
410     public static class ContentNode {
411         public static final String NAME = MGNL_PREFIX + "contentNode";
412     }
413 
414     /**
415      * Represents the nodeType mgnl:nodeData.
416      */
417     public static class NodeData {
418         public static final String NAME = MGNL_PREFIX + "nodeData";
419     }
420 
421     /**
422      * Represents the nodeType mgnl:page.
423      */
424     public static class Page {
425         public static final String NAME = MGNL_PREFIX + "page";
426     }
427 
428     /**
429      * Represents the nodeType mgnl:area.
430      */
431     public static class Area {
432         public static final String NAME = MGNL_PREFIX + "area";
433     }
434 
435     /**
436      * Represents the nodeType mgnl:component.
437      */
438     public static class Component {
439         public static final String NAME = MGNL_PREFIX + "component";
440     }
441 
442     /**
443      * Represents the nodeType mgnl:user.
444      */
445     public static class User {
446         public static final String NAME = MGNL_PREFIX + "user";
447     }
448 
449     /**
450      * Represents the nodeType mgnl:role.
451      */
452     public static class Role {
453         public static final String NAME = MGNL_PREFIX + "role";
454     }
455 
456     /**
457      * Represents the nodeType mgnl:group.
458      */
459     public static class Group {
460         public static final String NAME = MGNL_PREFIX + "group";
461     }
462 
463     /**
464      * Represents the nodeType mgnl:reserve.
465      */
466     public static class System {
467         public static final String NAME = MGNL_PREFIX + "reserve";
468     }
469 
470     /**
471      * Represents the nodeType mgnl:metaData.
472      * Is basically obsolete since MetaData as mixin but could still be used in customers code,
473      * hence it has to stay for quite a while.
474      */
475     public static class MetaData {
476         public static final String NAME = MGNL_PREFIX + "metaData";
477     }
478 
479     /**
480      * Mixin that marks node that have versions.
481      */
482     public static class HasVersion {
483         public static final String NAME = MGNL_PREFIX + "hasVersion";
484     }
485 
486     protected static String getCurrentUserName() {
487         String userName = MgnlContext.getUser().getName();
488         if (MgnlContext.isSystemInstance()) {
489             // in system context try to obtain original non-system context and retrieve user from it
490             Context ctx = ((SystemContext) MgnlContext.getInstance()).getOriginalContext();
491             if (ctx != null && ctx.getUser() != null && !userName.equals(ctx.getUser().getName())) {
492                 // log user and prepend "system" to the name to show in audit log that action was executed via system context w/o user really having privileges set to do such op on his own
493                 return "System [" + ctx.getUser().getName() + "]";
494             }
495         }
496         return userName;
497     }
498 
499     protected static Calendar getCurrentCalendar() {
500         return Calendar.getInstance();
501     }
502 
503     public static void checkNodeType(Node node, String nodeType, String... propertyNames) throws RepositoryException {
504         if (!node.isNodeType(nodeType)) {
505             log.warn("Trying to set property/ies '{}' although the node '{}' with PrimaryType '{}' is not of type '{}'!", propertyNames, node.getPath(), node.getPrimaryNodeType().getName(), nodeType);
506         }
507     }
508 }