View Javadoc

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