View Javadoc

1   /**
2    * This file Copyright (c) 2011-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.module.exchangesimple;
35  
36  import static info.magnolia.module.exchangesimple.BaseSyndicatorImpl.*;
37  
38  import info.magnolia.cms.core.Content;
39  import info.magnolia.cms.core.ItemType;
40  import info.magnolia.cms.core.Path;
41  import info.magnolia.cms.core.SystemProperty;
42  import info.magnolia.cms.security.SecurityUtil;
43  import info.magnolia.cms.util.Rule;
44  import info.magnolia.cms.util.RuleBasedContentFilter;
45  import info.magnolia.jcr.wrapper.DelegateNodeWrapper;
46  import info.magnolia.repository.RepositoryConstants;
47  
48  import java.io.File;
49  import java.io.FileInputStream;
50  import java.io.FileOutputStream;
51  import java.io.IOException;
52  import java.io.OutputStream;
53  import java.security.DigestOutputStream;
54  import java.util.Iterator;
55  import java.util.List;
56  import java.util.zip.GZIPOutputStream;
57  
58  import javax.jcr.Node;
59  import javax.jcr.RepositoryException;
60  import javax.jcr.Session;
61  
62  import org.apache.commons.io.IOUtils;
63  import org.apache.xml.serialize.OutputFormat;
64  import org.apache.xml.serialize.XMLSerializer;
65  import org.jdom.Document;
66  import org.jdom.Element;
67  import org.jdom.output.XMLOutputter;
68  import org.slf4j.Logger;
69  import org.slf4j.LoggerFactory;
70  import org.xml.sax.InputSource;
71  import org.xml.sax.SAXException;
72  import org.xml.sax.XMLReader;
73  import org.xml.sax.helpers.XMLReaderFactory;
74  
75  /**
76   * Class responsible for collecting all the resources for activation of content.
77   * 
78   * @version $Id$
79   */
80  public class ResourceCollector {
81      private static final Logger log = LoggerFactory.getLogger(ResourceCollector.class);
82  
83      /**
84       * Collects all information about activated content and its children (those that are set to be activated with the parent by filter rules).
85       * 
86       * @param contentFilterRule
87       * @param repositoryName
88       * @param workspaceName
89       * @param parent
90       * @throws Exception
91       */
92      public ActivationContent collect(Content node, List<String> orderBefore, String parent, String workspaceName, String repositoryName, Rule contentFilterRule) throws Exception {
93          Content.ContentFilter contentFilter = getContentFilter(contentFilterRule);
94  
95          // make sure resource file is unique
96          File resourceFile = File.createTempFile("resources", ".xml", Path.getTempDirectory());
97  
98          ActivationContent activationContent = new ActivationContent();
99          // add global properties true for this path/hierarchy
100         activationContent.addProperty(PARENT_PATH, parent);
101         activationContent.addProperty(WORKSPACE_NAME, workspaceName);
102         activationContent.addProperty(REPOSITORY_NAME, repositoryName);
103         activationContent.addProperty(RESOURCE_MAPPING_FILE, resourceFile.getName());// "resources.xml");
104         activationContent.addProperty(ACTION, ACTIVATE);
105         activationContent.addProperty(CONTENT_FILTER_RULE, contentFilterRule.toString());
106         activationContent.addProperty(NODE_UUID, node.getUUID());
107         activationContent.addProperty(UTF8_STATUS, SystemProperty.getProperty(SystemProperty.MAGNOLIA_UTF8_ENABLED));
108 
109         Document document = new Document();
110         Element root = new Element(RESOURCE_MAPPING_ROOT_ELEMENT);
111         document.setRootElement(root);
112         // collect exact order of this node within its same nodeType siblings
113         addOrderingInfo(root, orderBefore);
114 
115         this.addResources(root, node.getWorkspace().getSession(), node, contentFilter, activationContent);
116         XMLOutputter outputter = new XMLOutputter();
117 
118         DigestOutputStream outputStream = SecurityUtil.getDigestOutputStream(new FileOutputStream(resourceFile));
119         outputter.output(document, outputStream);
120         // add resource file to the list
121         activationContent.addFile(resourceFile.getName(), resourceFile);
122         // add signature of the resource file itself
123         activationContent.addProperty(RESOURCE_MAPPING_MD_ATTRIBUTE, SecurityUtil.getMD5Hex(outputStream));
124 
125         // add deletion info
126         activationContent.addProperty(ItemType.DELETED_NODE_MIXIN, "" + node.hasMixin(ItemType.DELETED_NODE_MIXIN));
127 
128         return activationContent;
129     }
130 
131     protected Content.ContentFilter getContentFilter(Rule contentFilterRule) {
132         return new RuleBasedContentFilter(contentFilterRule);
133     }
134 
135     /**
136      * Adds ordering information to the resource mapping file.
137      * 
138      * @param root
139      * element of the resource file under which ordering info must be added
140      * @param orderBefore
141      */
142     protected void addOrderingInfo(Element root, List<String> orderBefore) {
143         // do not use magnolia Content class since these objects are only meant for a single use to read UUID
144         Element siblingRoot = new Element(SIBLINGS_ROOT_ELEMENT);
145         root.addContent(siblingRoot);
146         if (orderBefore == null) {
147             return;
148         }
149         Iterator<String> siblings = orderBefore.iterator();
150         while (siblings.hasNext()) {
151             String uuid = siblings.next();
152             Element e = new Element(SIBLINGS_ELEMENT);
153             e.setAttribute(SIBLING_UUID, uuid);
154             siblingRoot.addContent(e);
155         }
156     }
157 
158     protected void addResources(Element resourceElement, Session session, final Content content, Content.ContentFilter filter, ActivationContent activationContent) throws IOException, RepositoryException, SAXException, Exception {
159         final String workspaceName = content.getWorkspace().getName();
160 
161         log.debug("Preparing content {}:{} for publishing.", new String[] { workspaceName, content.getHandle() });
162         final String uuid = content.getUUID();
163 
164         File file = File.createTempFile("exchange_" + uuid, ".xml.gz", Path.getTempDirectory());
165 
166         final DigestOutputStream outputStream = SecurityUtil.getDigestOutputStream(new GZIPOutputStream(new FileOutputStream(file)));
167 
168         // TODO: remove the second check. It should not be necessary. The only safe way to identify the versioned node is by looking at its type since the type is mandated by spec. and the frozen nodes is what the filter below removes anyway
169         if (content.isNodeType("nt:frozenNode") || workspaceName.equals(RepositoryConstants.VERSION_STORE)) {
170             XMLReader elementfilter = new FrozenElementFilter(XMLReaderFactory
171                     .createXMLReader(org.apache.xerces.parsers.SAXParser.class.getName()));
172             ((FrozenElementFilter) elementfilter).setNodeName(content.getName());
173             /**
174              * nt:file node type has mandatory sub nodes
175              */
176             boolean noRecurse = !content.isNodeType(ItemType.NT_FILE);
177             exportAndParse(session, content, elementfilter, outputStream, noRecurse);
178         } else {
179             /**
180              * nt:file node type has mandatory sub nodes
181              */
182             if (content.isNodeType(ItemType.NT_FILE)) {
183                 session.exportSystemView(content.getJCRNode().getPath(), outputStream, false, false);
184             } else {
185                 session.exportSystemView(content.getJCRNode().getPath(), outputStream, false, true);
186             }
187         }
188 
189         IOUtils.closeQuietly(outputStream);
190         // add file entry in mapping.xml
191         Element element = new Element(RESOURCE_MAPPING_FILE_ELEMENT);
192         element.setAttribute(RESOURCE_MAPPING_NAME_ATTRIBUTE, content.getName());
193         element.setAttribute(RESOURCE_MAPPING_UUID_ATTRIBUTE, uuid);
194         element.setAttribute(RESOURCE_MAPPING_ID_ATTRIBUTE, file.getName());
195 
196         element.setAttribute(RESOURCE_MAPPING_MD_ATTRIBUTE, SecurityUtil.getMD5Hex(outputStream));
197 
198         resourceElement.addContent(element);
199         // add this file element as resource in activation content
200         activationContent.addFile(file.getName(), file);
201 
202         Iterator<Content> children = content.getChildren(filter).iterator();
203         while (children.hasNext()) {
204             Content child = children.next();
205             this.addResources(element, session, child, filter, activationContent);
206         }
207     }
208 
209     /**
210      * Exports frozen resource. We can't export such resource directly, but need to filter out versioning information from the export.
211      */
212     protected void exportAndParse(Session session, Content content, XMLReader elementfilter, OutputStream os, boolean noRecurse) throws Exception {
213         File tempFile = File.createTempFile("Frozen_" + content.getName(), ".xml");
214         OutputStream tmpFileOutStream = null;
215         FileInputStream tmpFileInStream = null;
216         try {
217             tmpFileOutStream = new FileOutputStream(tempFile);
218             // has to get path via unwrapped JCR node since all else would have returned path to the base
219             Node node = content.getJCRNode();
220             if (node instanceof DelegateNodeWrapper) {
221                 node = ((DelegateNodeWrapper) node).getWrappedNode();
222             }
223             session.exportSystemView(node.getPath(), tmpFileOutStream, false, noRecurse);
224             tmpFileOutStream.flush();
225             tmpFileOutStream.close();
226 
227             OutputFormat outputFormat = new OutputFormat();
228             outputFormat.setPreserveSpace(false);
229 
230             tmpFileInStream = new FileInputStream(tempFile);
231             elementfilter.setContentHandler(new XMLSerializer(os, outputFormat));
232             elementfilter.parse(new InputSource(tmpFileInStream));
233             tmpFileInStream.close();
234         } catch (Throwable t) {
235             log.error("Failed to parse XML using FrozenElementFilter", t);
236             throw new Exception(t);
237         } finally {
238             IOUtils.closeQuietly(tmpFileInStream);
239             IOUtils.closeQuietly(tmpFileOutStream);
240             tempFile.delete();
241         }
242     }
243 
244     // @Inject
245     // public void setProperties(MagnoliaConfigurationProperties props) {
246     // this.properties = props;
247     // }
248 }