View Javadoc

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