View Javadoc
1   /**
2    * This file Copyright (c) 2003-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.cms.util;
35  
36  import info.magnolia.cms.core.Content;
37  import info.magnolia.cms.core.Content.ContentFilter;
38  import info.magnolia.cms.core.DefaultContent;
39  import info.magnolia.cms.core.FileSystemHelper;
40  import info.magnolia.cms.core.HierarchyManager;
41  import info.magnolia.cms.core.ItemType;
42  import info.magnolia.cms.security.AccessDeniedException;
43  import info.magnolia.context.MgnlContext;
44  import info.magnolia.jcr.util.NodeUtil;
45  import info.magnolia.objectfactory.Components;
46  
47  import java.io.File;
48  import java.io.FileInputStream;
49  import java.io.FileOutputStream;
50  import java.io.IOException;
51  import java.util.ArrayList;
52  import java.util.Collection;
53  import java.util.Comparator;
54  import java.util.Iterator;
55  import java.util.List;
56  import java.util.regex.Matcher;
57  import java.util.regex.Pattern;
58  
59  import javax.jcr.ImportUUIDBehavior;
60  import javax.jcr.Node;
61  import javax.jcr.PathNotFoundException;
62  import javax.jcr.RepositoryException;
63  import javax.jcr.Session;
64  
65  import org.apache.commons.io.FileUtils;
66  import org.apache.commons.io.IOUtils;
67  import org.apache.commons.lang3.StringUtils;
68  import org.slf4j.Logger;
69  import org.slf4j.LoggerFactory;
70  
71  /**
72   * Some easy to use methods to handle with Content objects.
73   *
74   * @deprecated since 4.5 - use {@link info.magnolia.jcr.util.NodeUtil} instead.
75   */
76  @Deprecated
77  public class ContentUtil {
78      private final static Logger log = LoggerFactory.getLogger(ContentUtil.class);
79  
80      /**
81       * Content filter accepting everything.
82       */
83      public static ContentFilter ALL_NODES_CONTENT_FILTER = new ContentFilter() {
84  
85          @Override
86          public boolean accept(Content content) {
87              return true;
88          }
89      };
90  
91      /**
92       * Content filter accepting everything exept nodes with namespace jcr (version and system store).
93       */
94      public static ContentFilter ALL_NODES_EXCEPT_JCR_CONTENT_FILTER = new ContentFilter() {
95  
96          @Override
97          public boolean accept(Content content) {
98              return !content.getName().startsWith("jcr:");
99          }
100     };
101 
102     /**
103      * Content filter accepting everything except meta data and jcr types.
104      */
105     public static ContentFilter EXCLUDE_META_DATA_CONTENT_FILTER = new ContentFilter() {
106         @Override
107         public boolean accept(Content content) {
108             return !content.getName().startsWith("jcr:") && !content.isNodeType(ItemType.NT_METADATA);
109         }
110     };
111 
112     /**
113      * Content filter accepting all nodes with a nodetype of namespace mgnl.
114      */
115     public static ContentFilter MAGNOLIA_FILTER = new ContentFilter() {
116         @Override
117         public boolean accept(Content content) {
118 
119             try {
120                 String nodetype = content.getNodeType().getName();
121                 // export only "magnolia" nodes
122                 return nodetype.startsWith("mgnl:");
123             } catch (RepositoryException e) {
124                 log.error("Unable to read nodetype for node {}", content.getHandle());
125             }
126             return false;
127         }
128     };
129 
130     /**
131      * Used in {@link #visit(Content)} to visit the hierarchy.
132      */
133     // TODO : throws RepositoryException or none, but not Exception !?
134     public static interface Visitor {
135         void visit(Content node) throws Exception;
136     }
137 
138     /**
139      * Used in {@link #visit(Content)} if the visitor wants to use post order.
140      */
141     public static interface PostVisitor extends Visitor {
142         void postVisit(Content node) throws Exception;
143     }
144 
145 
146     /**
147      * Returns a Content object of the named repository or null if not existing.
148      *
149      * @return null if not found
150      */
151     public static Content getContent(String repository, String path) {
152         try {
153             return HierarchyManagerUtil.asHierarchyManager(MgnlContext.getJCRSession(repository)).getContent(path);
154         } catch (RepositoryException e) {
155             return null;
156         }
157     }
158 
159     /**
160      * @return null if not found
161      */
162     public static Content getContentByUUID(String repository, String uuid) {
163         try {
164             return HierarchyManagerUtil.asHierarchyManager(MgnlContext.getJCRSession(repository)).getContentByUUID(uuid);
165         } catch (RepositoryException e) {
166             return null;
167         }
168     }
169 
170 
171     /**
172      * Get the node or null if not exists.
173      *
174      * @return the sub node
175      */
176     public static Content getContent(Content node, String name) {
177         try {
178             return node.getContent(name);
179         } catch (RepositoryException e) {
180             return null;
181         }
182     }
183 
184     /**
185      * If the node doesn't exist just create it. Attention the method does not save the newly created node.
186      */
187     public static Content getOrCreateContent(Content node, String name, ItemType contentType) throws AccessDeniedException, RepositoryException {
188         return getOrCreateContent(node, name, contentType, false);
189     }
190 
191     /**
192      * If the node doesn't exist just create it. If the parameter save is true the parent node is saved.
193      */
194     public static Content getOrCreateContent(Content node, String name, ItemType contentType, boolean save)
195             throws AccessDeniedException, RepositoryException {
196         Content res = null;
197         try {
198             res = node.getContent(name);
199         } catch (PathNotFoundException e) {
200             res = node.createContent(name, contentType);
201             if (save) {
202                 res.getParent().save();
203             }
204         }
205         return res;
206     }
207 
208     /**
209      * Get a subnode case insensitive.
210      */
211     public static Content getCaseInsensitive(Content node, String name) {
212         if (name == null || node == null) {
213             return null;
214         }
215         name = name.toLowerCase();
216         for (Content child : node.getChildren(ALL_NODES_CONTENT_FILTER)) {
217             if (child.getName().toLowerCase().equals(name)) {
218                 return child;
219             }
220         }
221         return null;
222     }
223 
224     /**
225      * Get all children recursively (content and contentnode).
226      */
227     public static List<Content> collectAllChildren(Content node) {
228         List<Content> nodes = new ArrayList<Content>();
229         return collectAllChildren(nodes, node, new ItemType[]{ItemType.CONTENT, ItemType.CONTENTNODE});
230     }
231 
232     /**
233      * Get all children using a filter.
234      *
235      * @return list of all found nodes
236      */
237     public static List<Content> collectAllChildren(Content node, ContentFilter filter) {
238         List<Content> nodes = new ArrayList<Content>();
239         return collectAllChildren(nodes, node, filter);
240     }
241 
242     /**
243      * Get the children using a filter.
244      *
245      * @param nodes collection of already found nodes
246      */
247     private static List<Content> collectAllChildren(List<Content> nodes, Content node, ContentFilter filter) {
248         // get filtered sub nodes first
249         Collection<Content> children = node.getChildren(filter);
250         for (Content child : children) {
251             nodes.add(child);
252         }
253 
254         // get all children to find recursively
255         Collection<Content> allChildren = node.getChildren(EXCLUDE_META_DATA_CONTENT_FILTER);
256 
257         // recursion
258         for (Content child : allChildren) {
259             collectAllChildren(nodes, child, filter);
260         }
261 
262         return nodes;
263     }
264 
265     /**
266      * Get all children of a particular type.
267      */
268     public static List<Content> collectAllChildren(Content node, ItemType type) {
269         List<Content> nodes = new ArrayList<Content>();
270         return collectAllChildren(nodes, node, new ItemType[]{type});
271     }
272 
273     /**
274      * Returns all children (not recursively) independent of there type.
275      */
276     public static Collection<Content> getAllChildren(Content node) {
277         return node.getChildren(EXCLUDE_META_DATA_CONTENT_FILTER);
278     }
279 
280     /**
281      * Returns all children (not recursively) independent of there type.
282      */
283     public static Collection<Content> getAllChildren(Content node, Comparator<Content> comp) {
284         return node.getChildren(EXCLUDE_META_DATA_CONTENT_FILTER, comp);
285     }
286 
287     /**
288      * Get all children of a particular type.
289      */
290     public static List<Content> collectAllChildren(Content node, ItemType[] types) {
291         List<Content> nodes = new ArrayList<Content>();
292         return collectAllChildren(nodes, node, types);
293     }
294 
295     /**
296      * Get all subnodes recursively and add them to the nodes collection.
297      */
298     private static List<Content> collectAllChildren(List<Content> nodes, Content node, ItemType[] types) {
299         for (int i = 0; i < types.length; i++) {
300             ItemType type = types[i];
301 
302             Collection<Content> children = node.getChildren(type);
303             for (Content child : children) {
304                 nodes.add(child);
305                 collectAllChildren(nodes, child, types);
306             }
307         }
308         return nodes;
309     }
310 
311     /**
312      * Returns the first found <strong>ancestor</strong> of the given node which is of the given type,
313      * or the given node itself, it is of the given type.
314      */
315     public static Content getAncestorOfType(final Content firstNode, final String nodeType) throws RepositoryException {
316         Content node = firstNode;
317         while (!node.isNodeType(nodeType)) {
318             node = node.getParent();
319             if (node.getLevel() == 0) {
320                 throw new RepositoryException("No ancestor of type " + nodeType + " found for " + firstNode);
321             }
322         }
323         return node;
324     }
325 
326     /**
327      * Convenient method to order a node before a target node.
328      */
329     public static void orderBefore(Content nodeToMove, String targetNodeName) throws RepositoryException {
330         nodeToMove.getParent().orderBefore(nodeToMove.getName(), targetNodeName);
331     }
332 
333     /**
334      * Convenient method for ordering a node after a specific target node. This is not that simple as jcr only supports ordering before a node.
335      *
336      * Caution: unlike {@link NodeUtil#orderAfter(Node, String)} this method won't throw an exception in case the targetNodeName does not exist.
337      */
338     public static void orderAfter(Content nodeToMove, String targetNodeName) throws RepositoryException {
339         Content parent = nodeToMove.getParent();
340         Boolean readyToMove = false;
341 
342         Collection<Content> children = new ArrayList<Content>(ContentUtil.getAllChildren(parent));
343         for (Content child : children) {
344             if (readyToMove) {
345                 parent.orderBefore(nodeToMove.getName(), child.getName());
346                 readyToMove = false;
347                 break;
348             }
349 
350             if (child.getName().equals(targetNodeName)) {
351                 readyToMove = true;
352             }
353         }
354 
355         if (readyToMove) {
356             for (Content child : children) {
357                 if (!nodeToMove.getName().equals(child.getName())) {
358                     parent.orderBefore(child.getName(), nodeToMove.getName());
359                 }
360             }
361         }
362     }
363 
364     public static void orderNodes(Content node, String[] nodes) throws RepositoryException {
365         for (int i = nodes.length - 1; i > 0; i--) {
366             node.orderBefore(nodes[i - 1], nodes[i]);
367         }
368     }
369 
370     /**
371      * Uses the passed comparator to create the jcr ordering of the children.
372      */
373     public static void orderNodes(Content node, Comparator<Content> comparator) throws RepositoryException {
374         Collection<Content> children = ContentUtil.getAllChildren(node, comparator);
375         String[] names = new String[children.size()];
376 
377         int i = 0;
378         for (Content childNode : children) {
379             names[i] = childNode.getName();
380             i++;
381         }
382         orderNodes(node, names);
383     }
384 
385     public static void visit(Content node, Visitor visitor) throws Exception {
386         visit(node, visitor, EXCLUDE_META_DATA_CONTENT_FILTER);
387     }
388 
389     public static void visit(Content node, Visitor visitor, ContentFilter filter) throws Exception {
390         visitor.visit(node);
391         for (Content content : node.getChildren(filter)) {
392             visit(content, visitor, filter);
393         }
394         if (visitor instanceof PostVisitor) {
395             ((PostVisitor) visitor).postVisit(node);
396         }
397     }
398 
399     public static Content createPath(HierarchyManager hm, String path) throws AccessDeniedException,
400             PathNotFoundException, RepositoryException {
401         return createPath(hm, path, false);
402     }
403 
404     public static Content createPath(HierarchyManager hm, String path, boolean save) throws AccessDeniedException,
405             PathNotFoundException, RepositoryException {
406         return createPath(hm, path, ItemType.CONTENT, save);
407     }
408 
409     public static Content createPath(HierarchyManager hm, String path, ItemType type) throws AccessDeniedException,
410             PathNotFoundException, RepositoryException {
411         return createPath(hm, path, type, false);
412     }
413 
414     public static Content createPath(HierarchyManager hm, String path, ItemType type, boolean save) throws AccessDeniedException,
415             PathNotFoundException, RepositoryException {
416         Content root = hm.getRoot();
417         return createPath(root, path, type, save);
418     }
419 
420     public static Content createPath(Content parent, String path, ItemType type) throws RepositoryException,
421             PathNotFoundException, AccessDeniedException {
422         return createPath(parent, path, type, false);
423     }
424 
425     public static Content createPath(Content parent, String path, ItemType type, boolean save) throws RepositoryException,
426             PathNotFoundException, AccessDeniedException {
427         // remove leading /
428         path = StringUtils.removeStart(path, "/");
429 
430         if (StringUtils.isEmpty(path)) {
431             return parent;
432         }
433 
434         String[] names = path.split("/");
435 
436         for (int i = 0; i < names.length; i++) {
437             String name = names[i];
438             if (parent.hasContent(name)) {
439                 parent = parent.getContent(name);
440             } else {
441                 final Content newNode = parent.createContent(name, type);
442                 if (save) {
443                     parent.save();
444                 }
445                 parent = newNode;
446             }
447         }
448         return parent;
449     }
450 
451     public static String uuid2path(String repository, String uuid) {
452         if (StringUtils.isNotEmpty(uuid)) {
453             HierarchyManager hm = HierarchyManagerUtil.getHierarchyManager(MgnlContext.getInstance(), repository);
454             try {
455                 Content node = hm.getContentByUUID(uuid);
456                 return node.getHandle();
457             } catch (Exception e) {
458                 // return the uuid
459             }
460 
461         }
462         return uuid;
463     }
464 
465     public static String path2uuid(String repository, String path) {
466         if (StringUtils.isNotEmpty(path)) {
467             HierarchyManager hm = HierarchyManagerUtil.getHierarchyManager(MgnlContext.getInstance(), repository);
468             try {
469                 Content node = hm.getContent(path);
470                 return node.getUUID();
471             } catch (Exception e) {
472                 // return the uuid
473             }
474 
475         }
476         return path;
477     }
478 
479     public static void deleteAndRemoveEmptyParents(Content node) throws PathNotFoundException, RepositoryException,
480             AccessDeniedException {
481         deleteAndRemoveEmptyParents(node, 0);
482     }
483 
484     public static void deleteAndRemoveEmptyParents(Content node, int level) throws PathNotFoundException, RepositoryException,
485             AccessDeniedException {
486         Content parent = null;
487         if (node.getLevel() != 0) {
488             parent = node.getParent();
489         }
490         node.delete();
491         if (parent != null && parent.getLevel() > level && parent.getChildren(ContentUtil.EXCLUDE_META_DATA_CONTENT_FILTER).size() == 0) {
492             deleteAndRemoveEmptyParents(parent, level);
493         }
494     }
495 
496     /**
497      * Session based copy operation. As JCR only supports workspace based copies this operation is performed
498      * by using export import operations.
499      * If no content exists one level above destAbsPath (in other words, there is no content that will serve as the parent of the moved item) then a PathNotFoundException is thrown either immediately, on dispatch or on persist. <br>
500      * Note that if content already exists at destAbsPath, the operation succeeds, <b>but the original properties and children values are kept</b>. No merge or override are performed.
501      */
502     public static void copyInSession(Content src, String dest) throws RepositoryException {
503         NodeUtil.copyInSession(src.getJCRNode(), dest);
504     }
505 
506     /**
507      * Magnolia uses by default workspace move operation to move nodes. This is a util method to move a node inside a session.
508      */
509     public static void moveInSession(Content src, String dest) throws RepositoryException {
510         src.getWorkspace().getSession().move(src.getHandle(), dest);
511     }
512 
513     public static void rename(Content node, String newName) throws RepositoryException {
514         Content parent = node.getParent();
515         String placedBefore = null;
516         for (Iterator<Content> iter = parent.getChildren(node.getNodeTypeName()).iterator(); iter.hasNext(); ) {
517             Content child = iter.next();
518             if (child.getUUID().equals(node.getUUID())) {
519                 if (iter.hasNext()) {
520                     child = iter.next();
521                     placedBefore = child.getName();
522                 }
523             }
524         }
525 
526         moveInSession(node, PathUtil.createPath(node.getParent().getHandle(), newName));
527 
528         // now set at the same place as before
529         if (placedBefore != null) {
530             parent.orderBefore(newName, placedBefore);
531             parent.save();
532         }
533     }
534 
535     /**
536      * Utility method to change the <code>jcr:primaryType</code> value of a node.
537      *
538      * @param node - {@link Content} the node whose type has to be changed
539      * @param newType - {@link ItemType} the new node type to be assigned
540      * @param replaceAll - boolean when <code>true</code> replaces all occurrences
541      * of the old node type. When <code>false</code> replaces only the first occurrence.
542      */
543     public static void changeNodeType(Content node, ItemType newType, boolean replaceAll) throws RepositoryException {
544         if (node == null) {
545             throw new IllegalArgumentException("Content can't be null");
546         }
547         if (newType == null) {
548             throw new IllegalArgumentException("ItemType can't be null");
549         }
550         final String oldTypeName = node.getNodeTypeName();
551         final String newTypeName = newType.getSystemName();
552         if (newTypeName.equals(oldTypeName)) {
553             log.info("Old node type and new one are the same {}. Nothing to change.", newTypeName);
554             return;
555         }
556         final Pattern nodeTypePattern = Pattern.compile("(<sv:property\\s+sv:name=\"jcr:primaryType\"\\s+sv:type=\"Name\"><sv:value>)(" + oldTypeName + ")(</sv:value>)");
557         final String replacement = "$1" + newTypeName + "$3";
558 
559         log.debug("pattern is {}", nodeTypePattern.pattern());
560         log.debug("replacement string is {}", replacement);
561         log.debug("replaceAll? {}", replaceAll);
562 
563         final String destParentPath = StringUtils.defaultIfEmpty(StringUtils.substringBeforeLast(node.getHandle(), "/"), "/");
564         final Session session = node.getWorkspace().getSession();
565         FileOutputStream outStream = null;
566         FileInputStream inStream = null;
567         File file = null;
568 
569         try {
570             file = File.createTempFile("mgnl", null, Components.getComponent(FileSystemHelper.class).getTempDirectory());
571             outStream = new FileOutputStream(file);
572             session.exportSystemView(node.getHandle(), outStream, false, false);
573             outStream.flush();
574             final String fileContents = FileUtils.readFileToString(file);
575             log.debug("content string is {}", fileContents);
576             final Matcher matcher = nodeTypePattern.matcher(fileContents);
577             String replaced = null;
578 
579             log.debug("starting find&replace...");
580             long start = System.currentTimeMillis();
581             if (matcher.find()) {
582                 log.debug("{} will be replaced", node.getHandle());
583                 if (replaceAll) {
584                     replaced = matcher.replaceAll(replacement);
585                 } else {
586                     replaced = matcher.replaceFirst(replacement);
587                 }
588                 log.debug("replaced string is {}", replaced);
589             } else {
590                 log.debug("{} won't be replaced", node.getHandle());
591                 return;
592             }
593             log.debug("find&replace operations took {}ms", (System.currentTimeMillis() - start));
594 
595             FileUtils.writeStringToFile(file, replaced);
596             inStream = new FileInputStream(file);
597             session.importXML(
598                     destParentPath,
599                     inStream,
600                     ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
601 
602         } catch (IOException e) {
603             throw new RepositoryException("Can't replace node " + node.getHandle(), e);
604         } finally {
605             IOUtils.closeQuietly(outStream);
606             IOUtils.closeQuietly(inStream);
607             FileUtils.deleteQuietly(file);
608         }
609     }
610 
611     public static Content asContent(Node content) {
612         if (content == null) {
613             return null;
614         }
615         return new DefaultContent(content);
616     }
617 }