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         public static void remove(Node node) throws RepositoryException {
133             if (node.hasProperty(LAST_MODIFIED)) {
134                 node.getProperty(LAST_MODIFIED).remove();
135             }
136             if (node.hasProperty(LAST_MODIFIED_BY)) {
137                 node.getProperty(LAST_MODIFIED_BY).remove();
138             }
139         }
140     }
141 
142     /**
143      * Represents the mixin mgnl:activatable.
144      */
145     public static class Activatable {
146         public static final String NAME = MGNL_PREFIX + "activatable";
147         public static final String LAST_ACTIVATED = MGNL_PREFIX + "lastActivated";
148         public static final String LAST_ACTIVATED_BY = LAST_ACTIVATED + BY;
149         public static final String LAST_ACTIVATED_VERSION = LAST_ACTIVATED + "Version";
150         public static final String LAST_ACTIVATED_VERSION_CREATED = LAST_ACTIVATED_VERSION + "Created";
151         public static final String ACTIVATION_STATUS = MGNL_PREFIX + "activationStatus";
152 
153         public static final int ACTIVATION_STATUS_NOT_ACTIVATED = 0;
154 
155         public static final int ACTIVATION_STATUS_MODIFIED = 1;
156 
157         public static final int ACTIVATION_STATUS_ACTIVATED = 2;
158 
159         /**
160          * Returns the activation status of the node. Returns one of the constants:
161          * <ul>
162          * <li>{@link #ACTIVATION_STATUS_NOT_ACTIVATED} if the node has not been activated</li>
163          * <li>{@link #ACTIVATION_STATUS_MODIFIED} has been activated and subsequently modified</li>
164          * <li>{@link #ACTIVATION_STATUS_ACTIVATED} has been activated and not modified since</li>
165          * </ul>
166          */
167         public static int getActivationStatus(Node node) throws RepositoryException {
168 
169             if (!isActivated(node)) {
170                 // never activated or deactivated
171                 return ACTIVATION_STATUS_NOT_ACTIVATED;
172             }
173 
174             Calendar lastModified = LastModified.getLastModified(node);
175             Calendar lastActivatedVersionCreated = null;
176 
177             if (!NodeUtil.isNodeType(node, Versionable.NAME)) {
178                 Node versionableNode = NodeUtil.getNearestAncestorOfType(node, Versionable.NAME);
179                 if (versionableNode != null) {
180                     lastActivatedVersionCreated = getLastActivatedVersionCreated(versionableNode);
181                 }
182             } else {
183                 lastActivatedVersionCreated = getLastActivatedVersionCreated(node);
184             }
185 
186             Calendar lastActivated = lastActivatedVersionCreated != null ? lastActivatedVersionCreated : getLastActivated(node);
187 
188             if (lastModified != null && lastModified.after(lastActivated)) {
189                 // node has been modified after last activation
190                 return ACTIVATION_STATUS_MODIFIED;
191             }
192 
193             // activated and not modified ever since
194             return ACTIVATION_STATUS_ACTIVATED;
195         }
196 
197         /**
198          * Returns true if the node has been activated.
199          */
200         public static boolean isActivated(Node node) throws RepositoryException {
201             return node.hasProperty(ACTIVATION_STATUS) && node.getProperty(ACTIVATION_STATUS).getBoolean();
202         }
203 
204         /**
205          * Returns the date when the node was last activated or null if no activation date has been stored on the node.
206          */
207         public static Calendar getLastActivated(Node node) throws RepositoryException {
208             return node.hasProperty(LAST_ACTIVATED) ? node.getProperty(LAST_ACTIVATED).getDate() : null;
209         }
210 
211         /**
212          * Returns the name of the user that last activated the node or null if no activating user has been stored on the node.
213          */
214         public static String getLastActivatedBy(Node node) throws RepositoryException {
215             return node.hasProperty(LAST_ACTIVATED_BY) ? node.getProperty(LAST_ACTIVATED_BY).getString() : null;
216         }
217 
218         /**
219          * Returns the latest activated version name or null if the version name isn't set.
220          */
221         public static String getLastActivatedVersion(Node node) throws RepositoryException {
222             return node.hasProperty(LAST_ACTIVATED_VERSION) ? node.getProperty(LAST_ACTIVATED_VERSION).getString() : null;
223         }
224 
225         /**
226          * Returns the latest activated version creation time or null if the version creation time isn't set.
227          */
228         public static Calendar getLastActivatedVersionCreated(Node node) throws RepositoryException {
229             return node.hasProperty(LAST_ACTIVATED_VERSION_CREATED) ? node.getProperty(LAST_ACTIVATED_VERSION_CREATED).getDate() : null;
230         }
231 
232         /**
233          * Sets the name of the user that performed the most recent activation as well as to current time.
234          */
235         public static void update(Node node, String userName, boolean isActivated) throws RepositoryException {
236             update(node, userName, isActivated, (Version) null);
237         }
238 
239         /**
240          * Sets the name of the user that performed the most recent activation as well as to current time and last activated version name.
241          * @deprecated since 5.6.2, use {@link #update(Node, String, boolean, Version)}
242          */
243         @Deprecated
244         public static void update(Node node, String userName, boolean isActivated, String versionName) throws RepositoryException {
245             node.setProperty(LAST_ACTIVATED_VERSION, versionName);
246             update(node, userName, isActivated, (Version) null);
247         }
248 
249         /**
250          * 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.
251          */
252         public static void update(Node node, String userName, boolean isActivated, Version version) throws RepositoryException {
253             checkNodeType(node, Activatable.NAME, LAST_ACTIVATED, LAST_ACTIVATED_BY, ACTIVATION_STATUS, LAST_ACTIVATED_VERSION);
254             node.setProperty(LAST_ACTIVATED, getCurrentCalendar());
255             node.setProperty(LAST_ACTIVATED_BY, userName);
256             node.setProperty(ACTIVATION_STATUS, isActivated);
257             if (version != null) {
258                 node.setProperty(LAST_ACTIVATED_VERSION, version.getName());
259                 node.setProperty(LAST_ACTIVATED_VERSION_CREATED, version.getCreated());
260             } else {
261                 // node activated without version, we can safely remove these properties as the last activated version is
262                 // outdated at this point of time
263                 node.setProperty(LAST_ACTIVATED_VERSION, (String) null);
264                 node.setProperty(LAST_ACTIVATED_VERSION_CREATED, (String) null);
265             }
266         }
267 
268         public static void remove(Node node) throws RepositoryException {
269             if (node.hasProperty(ACTIVATION_STATUS)) {
270                 node.getProperty(ACTIVATION_STATUS).remove();
271             }
272             if (node.hasProperty(LAST_ACTIVATED)) {
273                 node.getProperty(LAST_ACTIVATED).remove();
274             }
275             if (node.hasProperty(LAST_ACTIVATED_BY)) {
276                 node.getProperty(LAST_ACTIVATED_BY).remove();
277             }
278             if (node.hasProperty(LAST_ACTIVATED_VERSION)) {
279                 node.getProperty(LAST_ACTIVATED_VERSION).remove();
280             }
281             if (node.hasProperty(LAST_ACTIVATED_VERSION_CREATED)) {
282                 node.getProperty(LAST_ACTIVATED_VERSION_CREATED).remove();
283             }
284         }
285     }
286 
287     /**
288      * Represents the mixin mgnl:created.
289      */
290     public static class Created {
291         public static final String NAME = MGNL_PREFIX + "created";
292         public static final String CREATED = NAME;
293         public static final String CREATED_BY = CREATED + BY;
294 
295         /**
296          * Returns the creation date of a node or null if creation date isn't set.
297          */
298         public static Calendar getCreated(Node node) throws RepositoryException {
299             return node.hasProperty(CREATED) ? node.getProperty(CREATED).getDate() : null;
300         }
301 
302         /**
303          * Returns the name of the user that created a node.
304          */
305         public static String getCreatedBy(Node node) throws RepositoryException {
306             return node.hasProperty(CREATED_BY) ? node.getProperty(CREATED_BY).getString() : null;
307         }
308 
309         /**
310          * Sets the current date as the node's creation date and uses {@link info.magnolia.context.MgnlContext} to set the name of the creating
311          * user. Used with nodes having the <code>mgnl:created</code> mixin.
312          */
313         public static void set(Node node) throws RepositoryException {
314             set(node, getCurrentUserName(), getCurrentCalendar());
315         }
316 
317         /**
318          * Sets the supplied date as the node's creation date and sets the name of the creating user. Also sets the date of
319          * modification and the user last having modified the node to the same values. Used with nodes having the
320          * <code>mgnl:created</code> mixin.
321          */
322         public static void set(Node node, String userName, Calendar created) throws RepositoryException {
323             checkNodeType(node, NAME, CREATED, CREATED_BY);
324             node.setProperty(CREATED, created);
325             node.setProperty(CREATED_BY, userName);
326 
327             LastModified.update(node, userName, created);
328         }
329     }
330 
331     /**
332      * Represents the mixin mgnl:renderable.
333      */
334     public static class Renderable {
335         public static final String NAME = MGNL_PREFIX + "renderable";
336         public static final String TEMPLATE = MGNL_PREFIX + "template";
337 
338         /**
339          * Returns the template assigned to the node or null of none has been assigned. Used with nodes having the
340          * <code>mgnl:renderable</code> mixin.
341          */
342         public static String getTemplate(Node node) throws RepositoryException {
343             return node.hasProperty(TEMPLATE) ? node.getProperty(TEMPLATE).getString() : null;
344         }
345 
346         /**
347          * Sets the template assigned to the node. Used with nodes having the <code>mgnl:renderable</code> mixin.
348          */
349         public static void set(Node node, String template) throws RepositoryException {
350             checkNodeType(node, NAME, TEMPLATE);
351             node.setProperty(TEMPLATE, template);
352         }
353     }
354 
355     /**
356      * Represents the mixin mgnl:deleted.
357      */
358     public static class Deleted {
359         public static final String NAME = MGNL_PREFIX + "deleted";
360         public static final String DELETED = NAME;
361         public static final String DELETED_BY = DELETED + BY;
362         public static final String COMMENT = MGNL_PREFIX + "comment";
363 
364         /**
365          * Returns the date when the node was deleted or null if no deletion date has been stored on the node.
366          */
367         public static Calendar getDeleted(Node node) throws RepositoryException {
368             return node.hasProperty(DELETED) ? node.getProperty(DELETED).getDate() : null;
369         }
370 
371         /**
372          * Returns the name of the user that deleted the node or null if no deleting user has been stored on the node.
373          */
374         public static String getDeletedBy(Node node) throws RepositoryException {
375             return node.hasProperty(DELETED_BY) ? node.getProperty(DELETED_BY).getString() : null;
376         }
377 
378         /**
379          * Returns the comment set when then node was last deleted or null if no comment has been set.
380          */
381         public static String getComment(Node node) throws RepositoryException {
382             return node.hasProperty(COMMENT) ? node.getProperty(COMMENT).getString() : null;
383         }
384 
385         public static void set(Node node, String comment) throws RepositoryException {
386             checkNodeType(node, NAME, DELETED, DELETED_BY, COMMENT);
387             node.setProperty(DELETED, getCurrentCalendar());
388             node.setProperty(DELETED_BY, getCurrentUserName());
389             node.setProperty(COMMENT, comment);
390         }
391     }
392 
393     /**
394      * Represents the mixin mgnl:versionable.
395      */
396     public static class Versionable {
397         public static final String NAME = MGNL_PREFIX + "versionable";
398         public static final String COMMENT = Deleted.COMMENT;
399 
400         /**
401          * Returns the comment set when then node was last versioned or null if no comment has been set.
402          */
403         public static String getComment(Node node) throws RepositoryException {
404             return node.hasProperty(COMMENT) ? node.getProperty(COMMENT).getString() : null;
405         }
406 
407         /**
408          * Set the versioning comment on the node.
409          */
410         public static void set(Node node, String comment) throws RepositoryException {
411             checkNodeType(node, NAME, COMMENT);
412             node.setProperty(COMMENT, comment);
413         }
414     }
415 
416     /**
417      * Represents the nodeType mgnl:folder.
418      */
419     public static class Folder {
420         public static final String NAME = MGNL_PREFIX + "folder";
421     }
422 
423     /**
424      * Represents the nodeType mgnl:resource.
425      */
426     public static class Resource {
427         public static final String NAME = MGNL_PREFIX + "resource";
428     }
429 
430     /**
431      * Represents the nodeType mgnl:content.
432      */
433     public static class Content {
434         public static final String NAME = MGNL_PREFIX + "content";
435     }
436 
437     /**
438      * Represents the nodeType mgnl:contentNode.
439      */
440     public static class ContentNode {
441         public static final String NAME = MGNL_PREFIX + "contentNode";
442     }
443 
444     /**
445      * Represents the nodeType mgnl:nodeData.
446      */
447     public static class NodeData {
448         public static final String NAME = MGNL_PREFIX + "nodeData";
449     }
450 
451     /**
452      * Represents the nodeType mgnl:page.
453      */
454     public static class Page {
455         public static final String NAME = MGNL_PREFIX + "page";
456     }
457 
458     /**
459      * Represents the nodeType mgnl:area.
460      */
461     public static class Area {
462         public static final String NAME = MGNL_PREFIX + "area";
463     }
464 
465     /**
466      * Represents the nodeType mgnl:component.
467      */
468     public static class Component {
469         public static final String NAME = MGNL_PREFIX + "component";
470     }
471 
472     /**
473      * Represents the nodeType mgnl:user.
474      */
475     public static class User {
476         public static final String NAME = MGNL_PREFIX + "user";
477     }
478 
479     /**
480      * Represents the nodeType mgnl:role.
481      */
482     public static class Role {
483         public static final String NAME = MGNL_PREFIX + "role";
484     }
485 
486     /**
487      * Represents the nodeType mgnl:group.
488      */
489     public static class Group {
490         public static final String NAME = MGNL_PREFIX + "group";
491     }
492 
493     /**
494      * Represents the nodeType mgnl:reserve.
495      */
496     public static class System {
497         public static final String NAME = MGNL_PREFIX + "reserve";
498     }
499 
500     /**
501      * Represents the nodeType mgnl:metaData.
502      * Is basically obsolete since MetaData as mixin but could still be used in customers code,
503      * hence it has to stay for quite a while.
504      */
505     public static class MetaData {
506         public static final String NAME = MGNL_PREFIX + "metaData";
507     }
508 
509     /**
510      * Mixin that marks node that have versions.
511      */
512     public static class HasVersion {
513         public static final String NAME = MGNL_PREFIX + "hasVersion";
514     }
515 
516     protected static String getCurrentUserName() {
517         String userName = MgnlContext.getUser().getName();
518         if (MgnlContext.isSystemInstance()) {
519             // in system context try to obtain original non-system context and retrieve user from it
520             Context ctx = ((SystemContext) MgnlContext.getInstance()).getOriginalContext();
521             if (ctx != null && ctx.getUser() != null && !userName.equals(ctx.getUser().getName())) {
522                 // 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
523                 return "System [" + ctx.getUser().getName() + "]";
524             }
525         }
526         return userName;
527     }
528 
529     protected static Calendar getCurrentCalendar() {
530         return Calendar.getInstance();
531     }
532 
533     public static void checkNodeType(Node node, String nodeType, String... propertyNames) throws RepositoryException {
534         if (!node.isNodeType(nodeType)) {
535             log.warn("Trying to set property/ies '{}' although the node '{}' with PrimaryType '{}' is not of type '{}'!", propertyNames, node.getPath(), node.getPrimaryNodeType().getName(), nodeType);
536         }
537     }
538 }