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