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