View Javadoc

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