View Javadoc

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