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.module.exchangesimple;
35  
36  import info.magnolia.cms.beans.config.ContentRepository;
37  import info.magnolia.cms.core.Content;
38  import info.magnolia.cms.core.HierarchyManager;
39  import info.magnolia.cms.core.ItemType;
40  import info.magnolia.cms.core.MetaData;
41  import info.magnolia.cms.core.Path;
42  import info.magnolia.cms.core.SystemProperty;
43  import info.magnolia.cms.core.version.ContentVersion;
44  import info.magnolia.cms.exchange.ExchangeException;
45  import info.magnolia.cms.exchange.Subscriber;
46  import info.magnolia.cms.exchange.Subscription;
47  import info.magnolia.cms.exchange.Syndicator;
48  import info.magnolia.cms.security.AccessDeniedException;
49  import info.magnolia.cms.security.User;
50  import info.magnolia.cms.util.ContentUtil;
51  import info.magnolia.cms.util.Rule;
52  import info.magnolia.cms.util.RuleBasedContentFilter;
53  import info.magnolia.context.MgnlContext;
54  import info.magnolia.logging.AuditLoggingUtil;
55  
56  import java.io.File;
57  import java.io.FileInputStream;
58  import java.io.FileOutputStream;
59  import java.io.IOException;
60  import java.io.OutputStream;
61  import java.io.UnsupportedEncodingException;
62  import java.net.HttpURLConnection;
63  import java.net.MalformedURLException;
64  import java.net.URL;
65  import java.net.URLConnection;
66  import java.net.URLEncoder;
67  import java.util.Iterator;
68  import java.util.List;
69  import java.util.zip.GZIPOutputStream;
70  
71  import javax.jcr.RepositoryException;
72  import javax.jcr.Session;
73  
74  import org.apache.commons.codec.binary.Base64;
75  import org.apache.commons.io.IOUtils;
76  import org.apache.commons.lang.StringUtils;
77  import org.apache.xml.serialize.OutputFormat;
78  import org.apache.xml.serialize.XMLSerializer;
79  import org.jdom.Document;
80  import org.jdom.Element;
81  import org.jdom.output.XMLOutputter;
82  import org.slf4j.Logger;
83  import org.slf4j.LoggerFactory;
84  import org.xml.sax.InputSource;
85  import org.xml.sax.SAXException;
86  import org.xml.sax.XMLReader;
87  import org.xml.sax.helpers.XMLReaderFactory;
88  
89  import EDU.oswego.cs.dl.util.concurrent.Sync;
90  
91  /**
92   * Default implementation of {@link Syndicator}. Activates all the content to a subscriber configured on the server.
93   * @author Sameer Charles
94   * $Id: $
95   */
96  public abstract class BaseSyndicatorImpl implements Syndicator {
97      private static final Logger log = LoggerFactory.getLogger(BaseSyndicatorImpl.class);
98  
99      /**
100      * URI used for activation.
101      */
102     public static final String DEFAULT_HANDLER = ".magnolia/activation"; //$NON-NLS-1$
103 
104     public static final String PARENT_PATH = "mgnlExchangeParentPath";
105 
106     public static final String MAPPED_PARENT_PATH = "mgnlExchangeMappedParent";
107 
108     /**
109      * Path to be activated or deactivated.
110      */
111     public static final String PATH = "mgnlExchangePath";
112 
113     public static final String NODE_UUID = "mgnlExchangeNodeUUID";
114 
115     public static final String REPOSITORY_NAME = "mgnlExchangeRepositoryName";
116 
117     public static final String WORKSPACE_NAME = "mgnlExchangeWorkspaceName";
118 
119     public static final String VERSION_NAME = "mgnlExchangeVersionName";
120 
121     /**
122      * Name of the resource containing reading sequence for importing the data in activation target.
123      */
124     public static final String RESOURCE_MAPPING_FILE = "mgnlExchangeResourceMappingFile";
125 
126     public static final String UTF8_STATUS = "mgnlUTF8Status";
127 
128     /**
129      * Name of the element in the resource file describing siblings of activated node.
130      * Siblings element will contain all siblings of the same node type which are "before"
131      * this node.
132      */
133     public static final String SIBLINGS_ROOT_ELEMENT = "NodeSiblings";
134 
135     public static final String SIBLINGS_ELEMENT = "sibling";
136 
137     public static final String SIBLING_UUID = "siblingUUID";
138 
139     public static final String RESOURCE_MAPPING_FILE_ELEMENT = "File";
140 
141     public static final String RESOURCE_MAPPING_NAME_ATTRIBUTE = "name";
142 
143     public static final String RESOURCE_MAPPING_UUID_ATTRIBUTE = "contentUUID";
144 
145     public static final String RESOURCE_MAPPING_ID_ATTRIBUTE = "resourceId";
146 
147     public static final String RESOURCE_MAPPING_ROOT_ELEMENT = "Resources";
148 
149     public static final String ACTION = "mgnlExchangeAction";
150 
151     public static final String ACTIVATE = "activate"; //$NON-NLS-1$
152 
153     public static final String DEACTIVATE = "deactivate"; //$NON-NLS-1$
154 
155     public static final String COMMIT = "commit";
156 
157     public static final String ROLLBACK = "rollback";
158 
159     public static final String AUTHORIZATION = "Authorization";
160 
161     public static final String AUTH_CREDENTIALS= "mgnlUserPSWD";
162 
163     public static final String AUTH_USER = "mgnlUserId";
164 
165     public static final String CONTENT_FILTER_RULE = "mgnlExchangeFilterRule";
166 
167     public static final String ACTIVATION_SUCCESSFUL = "sa_success"; //$NON-NLS-1$
168 
169     public static final String ACTIVATION_FAILED = "sa_failed"; //$NON-NLS-1$
170 
171     public static final String ACTIVATION_ATTRIBUTE_STATUS = "sa_attribute_status"; //$NON-NLS-1$
172 
173     public static final String ACTIVATION_ATTRIBUTE_MESSAGE = "sa_attribute_message"; //$NON-NLS-1$
174 
175     public static final String ACTIVATION_ATTRIBUTE_VERSION = "sa_attribute_version"; //$NON-NLS-1$
176 
177     /**
178      * Runs a given job in the thread pool.
179      *
180      * @param job the job to run
181      * @throws ExchangeException if the job could not be put in the pool
182      */
183     protected static void executeInPool(Runnable job) throws ExchangeException {
184         try {
185             ThreadPool.getInstance().execute(job);
186         } catch (InterruptedException e) {
187             // this is kind of a problem, we could not add the job to the pool
188             // retrying might or might not work now that the interruption
189             // status is cleared but there is not much we can do so throwing
190             // an ExchangeException seems like the least bad choice
191             String message = "could not execute job in pool";
192             log.error(message, e);
193             throw new ExchangeException(message, e);
194         }
195     }
196 
197     /**
198      * Acquires a {@link Sync} ignoring any interruptions. Should any
199      * interruption occur the interruption status will be set. Might
200      * potentially block/wait forever.
201      *
202      * @see Sync#acquire()
203      *
204      * @param latch the latch on which to wait
205      */
206     protected static void acquireIgnoringInterruption(Sync latch) {
207         try {
208             latch.acquire();
209         } catch (InterruptedException e) {
210             // waken up externally - ignore try again
211             acquireIgnoringInterruption(latch);
212             // be a good citizen and set back the interruption status
213             Thread.currentThread().interrupt();
214         }
215     }
216 
217     protected String repositoryName;
218 
219     protected String workspaceName;
220 
221     protected String parent;
222 
223     protected Content.ContentFilter contentFilter;
224 
225     protected Rule contentFilterRule;
226 
227     protected User user;
228 
229     protected String basicCredentials;
230 
231     /**
232      * @param user
233      * @param repositoryName repository ID
234      * @param workspaceName workspace ID
235      * @param rule content filter rule
236      * @see info.magnolia.cms.exchange.Syndicator#init(info.magnolia.cms.security.User, String, String,
237      * info.magnolia.cms.util.Rule)
238      */
239     public void init(User user, String repositoryName, String workspaceName, Rule rule) {
240         this.user = user;
241         this.basicCredentials = "Basic "
242             + new String(Base64.encodeBase64((this.user.getName() + ":" + this.user.getPassword()).getBytes()));
243         this.contentFilter = new RuleBasedContentFilter(rule);
244         this.contentFilterRule = rule;
245         this.repositoryName = repositoryName;
246         this.workspaceName = workspaceName;
247     }
248 
249     /**
250      * This will activate specifies page (sub pages) to all configured subscribers.
251      *
252      * @param parent parent under which this page will be activated
253      * @param content to be activated
254      * @throws javax.jcr.RepositoryException
255      * @throws info.magnolia.cms.exchange.ExchangeException
256      */
257     public void activate(String parent, Content content) throws ExchangeException, RepositoryException {
258         this.activate(parent, content, null);
259     }
260 
261     /**
262      * This will activate specified node to all configured subscribers.
263      *
264      * @param parent parent under which this page will be activated
265      * @param content to be activated
266      * @param orderBefore List of UUID to be used by the implementation to order this node after activation
267      * @throws javax.jcr.RepositoryException
268      * @throws info.magnolia.cms.exchange.ExchangeException
269      *
270      */
271     public void activate(String parent, Content content, List<String> orderBefore) throws ExchangeException, RepositoryException {
272         this.activate(null, parent, content, orderBefore);
273     }
274 
275     /**
276      * This will activate specifies page (sub pages) to the specified subscriber.
277      *
278      * @param subscriber
279      * @param parent parent under which this page will be activated
280      * @param content to be activated
281      * @throws javax.jcr.RepositoryException
282      * @throws info.magnolia.cms.exchange.ExchangeException
283      */
284     public void activate(Subscriber subscriber, String parent, Content content) throws ExchangeException, RepositoryException {
285         this.activate(subscriber, parent, content, null);
286     }
287 
288     /**
289      * This will activate specifies node to the specified subscriber.
290      *
291      * @param subscriber
292      * @param parent      parent under which this page will be activated
293      * @param content     to be activated
294      * @param orderBefore List of UUID to be used by the subscriber to order this node after activation
295      * @throws javax.jcr.RepositoryException
296      * @throws info.magnolia.cms.exchange.ExchangeException
297      */
298     public void activate(Subscriber subscriber, String parent, Content content, List<String> orderBefore) throws ExchangeException, RepositoryException {
299         this.parent = parent;
300         String path = content.getHandle();
301         ActivationContent activationContent = null;
302         try {
303             activationContent = this.collect(content, orderBefore);
304             if (null == subscriber) {
305                 this.activate(activationContent, path);
306             } else {
307                 this.activate(subscriber, activationContent, path);
308             }
309             if (Boolean.parseBoolean(activationContent.getproperty(ItemType.DELETED_NODE_MIXIN))) {
310                 final HierarchyManager hm = content.getHierarchyManager();
311                 String uuid = content.getUUID();
312                 if (StringUtils.isNotBlank(uuid)) {
313                     if (content instanceof ContentVersion) {
314                         // replace versioned content with the real node
315                         content = hm.getContentByUUID(uuid);
316                     }
317                     Content parentContent = content.getParent();
318                     content.delete();
319                     parentContent.save();
320                 } else {
321                     log.warn("Content {}:{} was already removed.", new String[] {hm.getName(), path});
322                 }
323             } else {
324                 this.updateActivationDetails(path);
325             }
326             log.info("Exchange: activation succeeded [{}]", path);
327         } catch (Exception e) {
328             if (log.isDebugEnabled()) {
329                 log.error("Exchange: activation failed for path:" + ((path != null) ? path : "[null]"), e);
330                 long timestamp = System.currentTimeMillis();
331                 log.warn("moving files from failed activation to *.failed" + timestamp );
332                 Iterator<File> keys = activationContent.getFiles().values().iterator();
333                 while (keys.hasNext()) {
334                     File f = keys.next();
335                     f.renameTo(new File(f.getAbsolutePath()+".failed" + timestamp));
336                 }
337                 activationContent.getFiles().clear();
338 
339             }
340             throw new ExchangeException(e);
341         } finally {
342             log.debug("Cleaning temporary files");
343             cleanTemporaryStore(activationContent);
344         }
345     }
346 
347     /**
348      * @throws ExchangeException
349      */
350     public abstract void activate(ActivationContent activationContent, String nodePath) throws ExchangeException;
351 
352 
353     /**
354      * Send request of activation of activationContent to the subscriber. Subscriber might choose not to react if it is not subscribed to the URI under which activationContent exists.
355      */
356     public String activate(Subscriber subscriber, ActivationContent activationContent, String nodePath) throws ExchangeException {
357         // FYI: this method is invoked from multiple threads at a same time (one for each subscriber, activationContent is assumed to be NOT shared between threads (cloned or by other means replicated) )
358         log.debug("activate");
359         if (null == subscriber) {
360             throw new ExchangeException("Null Subscriber");
361         }
362 
363         String parentPath = null;
364 
365         // concurrency: from path and repo name are same for all subscribers
366         Subscription subscription = subscriber.getMatchedSubscription(nodePath, this.repositoryName);
367         if (null != subscription) {
368             // its subscribed since we found the matching subscription
369             parentPath = this.getMappedPath(this.parent, subscription);
370             activationContent.setProperty(PARENT_PATH, parentPath);
371         } else {
372             log.debug("Exchange : subscriber [{}] is not subscribed to {}", subscriber.getName(), nodePath);
373             return "not subscribed";
374         }
375         log.debug("Exchange : sending activation request to {} with user {}", subscriber.getName(), this.user.getName());
376 
377         URLConnection urlConnection = null;
378         String versionName = null;
379         try {
380             urlConnection = prepareConnection(subscriber, getActivationURL(subscriber));
381             this.addActivationHeaders(urlConnection, activationContent);
382 
383             Transporter.transport((HttpURLConnection) urlConnection, activationContent);
384 
385             String status = urlConnection.getHeaderField(ACTIVATION_ATTRIBUTE_STATUS);
386             versionName = urlConnection.getHeaderField(ACTIVATION_ATTRIBUTE_VERSION);
387 
388             // check if the activation failed
389             if (StringUtils.equals(status, ACTIVATION_FAILED)) {
390                 String message = urlConnection.getHeaderField(ACTIVATION_ATTRIBUTE_MESSAGE);
391                 throw new ExchangeException("Message received from subscriber: " + message);
392             }
393             urlConnection.getContent();
394             log.debug("Exchange : activation request sent to {}", subscriber.getName());
395         }
396         catch (ExchangeException e) {
397             throw e;
398         }
399         catch (IOException e) {
400             log.debug("Failed to transport following activated content {" + StringUtils.join(activationContent.getProperties().keySet().iterator(), ',') + "} due to " + e.getMessage(), e);
401             throw new ExchangeException("Not able to send the activation request [" + (urlConnection == null ? null : urlConnection.getURL()) + "]: " + e.getMessage(), e);
402         }
403         catch (Exception e) {
404             throw new ExchangeException(e);
405         }
406         return versionName;
407     }
408 
409 
410     /**
411      * Cleans up temporary file store after activation.
412      */
413     protected void cleanTemporaryStore(ActivationContent activationContent) {
414         if (activationContent == null) {
415             log.debug("Clean temporary store - nothing to do");
416             return;
417         }
418         if (log.isDebugEnabled()) {
419             log.debug("Debugging is enabled. Keeping temporary files in store for debugging purposes. Clean the store manually once done with debugging.");
420             return;
421         }
422 
423         Iterator<String> keys = activationContent.getFiles().keySet().iterator();
424         while (keys.hasNext()) {
425             String key = keys.next();
426             log.debug("Removing temporary file {}", key);
427             activationContent.getFile(key).delete();
428         }
429     }
430 
431     public synchronized void deactivate(String path) throws ExchangeException, RepositoryException {
432         final Content node = getHierarchyManager().getContent(path);
433         deactivate(node);
434     }
435 
436     /**
437      * @param node to deactivate
438      * @throws RepositoryException
439      * @throws ExchangeException
440      */
441     public synchronized void deactivate(Content node) throws ExchangeException, RepositoryException {
442         String nodeUUID = node.getUUID();
443         String path = node.getHandle();
444         this.doDeactivate(nodeUUID, path);
445         updateDeactivationDetails(nodeUUID);
446     }
447 
448     /**
449      * @param node , to deactivate
450      * @param subscriber
451      * @throws RepositoryException
452      * @throws ExchangeException
453      */
454     public synchronized void deactivate(Subscriber subscriber, Content node) throws ExchangeException, RepositoryException {
455         String nodeUUID = node.getUUID();
456         String path = node.getHandle();
457         this.doDeactivate(subscriber, nodeUUID, path);
458         updateDeactivationDetails(nodeUUID);
459     }
460 
461     /**
462      * @throws ExchangeException
463      */
464     public abstract void doDeactivate(String nodeUUID, String nodePath) throws ExchangeException;
465 
466     /**
467      * Deactivate content from specified subscriber.
468      * @param subscriber
469      * @throws ExchangeException
470      */
471     public abstract String doDeactivate(Subscriber subscriber, String nodeUUID, String nodePath) throws ExchangeException;
472 
473     /**
474      * Return URI set for deactivation.
475      * @param subscriberInfo
476      */
477     protected String getDeactivationURL(Subscriber subscriberInfo) {
478         return getActivationURL(subscriberInfo);
479     }
480 
481     /**
482      * Adds header fields describing deactivation request.
483      * @param connection
484      */
485     protected void addDeactivationHeaders(URLConnection connection, String nodeUUID) {
486         connection.addRequestProperty(REPOSITORY_NAME, this.repositoryName);
487         connection.addRequestProperty(WORKSPACE_NAME, this.workspaceName);
488         if (nodeUUID != null) {
489             connection.addRequestProperty(NODE_UUID, nodeUUID);
490         }
491         connection.addRequestProperty(ACTION, DEACTIVATE);
492     }
493 
494     /**
495      * Retrieves URL subscriber is listening on for (de)activation requests.
496      */
497     protected String getActivationURL(Subscriber subscriberInfo) {
498         final String url = subscriberInfo.getURL();
499         if (!url.endsWith("/")) {
500             return url + "/" + DEFAULT_HANDLER;
501         }
502         return url + DEFAULT_HANDLER;
503     }
504 
505     /**
506      * Adds headers fields describing activation request.
507      */
508     protected void addActivationHeaders(URLConnection connection, ActivationContent activationContent) {
509         Iterator<String> headerKeys = activationContent.getProperties().keySet().iterator();
510         while (headerKeys.hasNext()) {
511             String key = headerKeys.next();
512             String value = activationContent.getproperty(key);
513             if(SystemProperty.getBooleanProperty(SystemProperty.MAGNOLIA_UTF8_ENABLED)) {
514                 try {
515                     value = URLEncoder.encode(value, "UTF-8");
516                 }
517                 catch (UnsupportedEncodingException e) {
518                     // do nothing
519                 }
520             }
521             connection.setRequestProperty(key, value);
522         }
523     }
524 
525     /**
526      * Updates current content activation meta data with the time stamp and user details of the activation.
527      */
528     protected void updateActivationDetails(String path) throws RepositoryException {
529         // page activated already use system context to ensure meta data is activated even if activating user has no rights to the activated page children
530         Content page = getSystemHierarchyManager().getContent(path);
531         updateMetaData(page, ACTIVATE);
532         page.save();
533         AuditLoggingUtil.log(AuditLoggingUtil.ACTION_ACTIVATE, this.workspaceName, page.getItemType(), path );
534     }
535 
536     /**
537      * Updates current content activation meta data with the timestamp and user details of the deactivation.
538      */
539     protected void updateDeactivationDetails(String nodeUUID) throws RepositoryException {
540         // page deactivated already use system context to ensure meta data is activated even if activating user has no rights to the activated page children
541         Content page = getSystemHierarchyManager().getContentByUUID(nodeUUID);
542         updateMetaData(page, DEACTIVATE);
543         page.save();
544         AuditLoggingUtil.log(AuditLoggingUtil.ACTION_DEACTIVATE, this.workspaceName, page.getItemType(), page.getHandle() );
545     }
546 
547 
548     private HierarchyManager getHierarchyManager() {
549         return MgnlContext.getHierarchyManager(this.repositoryName, this.workspaceName);
550     }
551 
552     private HierarchyManager getSystemHierarchyManager() {
553         return MgnlContext.getSystemContext().getHierarchyManager(this.repositoryName, this.workspaceName);
554     }
555 
556     /**
557      * @param node
558      * @param type (activate / deactivate)
559      */
560     protected void updateMetaData(Content node, String type) throws AccessDeniedException {
561         // update the passed node
562         MetaData md = node.getMetaData();
563         if (type.equals(ACTIVATE)) {
564             md.setActivated();
565         }
566         else {
567             md.setUnActivated();
568         }
569         md.setActivatorId(this.user.getName());
570         md.setLastActivationActionDate();
571 
572         Iterator<Content> children;
573         if (type.equals(ACTIVATE)) {
574             // use syndicator rule based filter
575             children = node.getChildren(this.contentFilter).iterator();
576         }
577         else {
578             // all children
579             children = node.getChildren(ContentUtil.EXCLUDE_META_DATA_CONTENT_FILTER).iterator();
580         }
581 
582         while (children.hasNext()) {
583             Content child = children.next();
584             this.updateMetaData(child, type);
585         }
586 
587 
588     }
589 
590     /**
591      * Collects all information about activated content and its children (those that are set to be activated with the parent by filter rules).
592      * @throws Exception
593      */
594     protected ActivationContent collect(Content node, List<String> orderBefore) throws Exception {
595         // make sure resource file is unique
596         File resourceFile = File.createTempFile("resources", ".xml", Path.getTempDirectory());
597 
598         ActivationContent activationContent = new ActivationContent();
599         // add global properties true for this path/hierarchy
600         activationContent.addProperty(PARENT_PATH, this.parent);
601         activationContent.addProperty(WORKSPACE_NAME, this.workspaceName);
602         activationContent.addProperty(REPOSITORY_NAME, this.repositoryName);
603         activationContent.addProperty(RESOURCE_MAPPING_FILE, resourceFile.getName());//"resources.xml");
604         activationContent.addProperty(ACTION, ACTIVATE);
605         activationContent.addProperty(CONTENT_FILTER_RULE, this.contentFilterRule.toString());
606         activationContent.addProperty(NODE_UUID, node.getUUID());
607         activationContent.addProperty(UTF8_STATUS, SystemProperty.getProperty(SystemProperty.MAGNOLIA_UTF8_ENABLED));
608 
609 
610         Document document = new Document();
611         Element root = new Element(RESOURCE_MAPPING_ROOT_ELEMENT);
612         document.setRootElement(root);
613         // collect exact order of this node within its same nodeType siblings
614         addOrderingInfo(root, orderBefore);
615 
616         this.addResources(root, node.getWorkspace().getSession(), node, this.contentFilter, activationContent);
617         XMLOutputter outputter = new XMLOutputter();
618         outputter.output(document, new FileOutputStream(resourceFile));
619         // add resource file to the list
620         activationContent.addFile(resourceFile.getName(), resourceFile);
621 
622         // add deletion info
623         activationContent.addProperty(ItemType.DELETED_NODE_MIXIN, "" + node.hasMixin(ItemType.DELETED_NODE_MIXIN));
624 
625         return activationContent;
626     }
627 
628     /**
629      * Adds ordering information to the resource mapping file.
630      * @param root element of the resource file under which ordering info must be added
631      * @param orderBefore
632      */
633     protected void addOrderingInfo(Element root, List<String> orderBefore) {
634         //do not use magnolia Content class since these objects are only meant for a single use to read UUID
635         Element siblingRoot = new Element(SIBLINGS_ROOT_ELEMENT);
636         root.addContent(siblingRoot);
637         if (orderBefore == null) {
638             return;
639         }
640         Iterator<String> siblings = orderBefore.iterator();
641         while (siblings.hasNext()) {
642             String uuid = siblings.next();
643             Element e = new Element(SIBLINGS_ELEMENT);
644             e.setAttribute(SIBLING_UUID, uuid);
645             siblingRoot.addContent(e);
646         }
647     }
648 
649     protected void addResources(Element resourceElement, Session session, final Content content, Content.ContentFilter filter, ActivationContent activationContent) throws IOException, RepositoryException, SAXException, Exception {
650         final String workspaceName = content.getWorkspace().getName();
651         log.debug("Preparing content {}:{} for publishing.", new String[] {workspaceName, content.getHandle()});
652         final String uuid = content.getUUID();
653 
654         File file = File.createTempFile("exchange_" + uuid, ".xml.gz", Path.getTempDirectory());
655         GZIPOutputStream gzipOutputStream = new GZIPOutputStream(new FileOutputStream(file));
656 
657         // 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
658         if (content.isNodeType("nt:frozenNode") || workspaceName.equals(ContentRepository.VERSION_STORE)) {
659             XMLReader elementfilter = new FrozenElementFilter(XMLReaderFactory
660                     .createXMLReader(org.apache.xerces.parsers.SAXParser.class.getName()));
661             ((FrozenElementFilter) elementfilter).setNodeName(content.getName());
662             /**
663              * nt:file node type has mandatory sub nodes
664              */
665             boolean noRecurse = !content.isNodeType(ItemType.NT_FILE);
666             exportAndParse(session, content, elementfilter, gzipOutputStream, noRecurse);
667         } else {
668             /**
669              * nt:file node type has mandatory sub nodes
670              */
671             if (content.isNodeType(ItemType.NT_FILE)) {
672                 session.exportSystemView(content.getJCRNode().getPath(), gzipOutputStream, false, false);
673             } else {
674                 session.exportSystemView(content.getJCRNode().getPath(), gzipOutputStream, false, true);
675             }
676         }
677 
678         IOUtils.closeQuietly(gzipOutputStream);
679         // add file entry in mapping.xml
680         Element element = new Element(RESOURCE_MAPPING_FILE_ELEMENT);
681         element.setAttribute(RESOURCE_MAPPING_NAME_ATTRIBUTE, content.getName());
682         element.setAttribute(RESOURCE_MAPPING_UUID_ATTRIBUTE, uuid);
683         element.setAttribute(RESOURCE_MAPPING_ID_ATTRIBUTE, file.getName());
684         resourceElement.addContent(element);
685         // add this file element as resource in activation content
686         activationContent.addFile(file.getName(), file);
687 
688         Iterator<Content> children = content.getChildren(filter).iterator();
689         while (children.hasNext()) {
690             Content child = children.next();
691             this.addResources(element, session, child, filter, activationContent);
692         }
693     }
694 
695     protected void exportAndParse(Session session, Content content, XMLReader elementfilter, OutputStream os, boolean noRecurse) throws Exception {
696         File tempFile = File.createTempFile("Frozen_"+content.getName(), ".xml"); //$NON-NLS-1$ //$NON-NLS-2$
697         OutputStream tmpFileOutStream = null;
698         FileInputStream tmpFileInStream = null;
699         try {
700             tmpFileOutStream = new FileOutputStream(tempFile);
701             // has to get path via JCR node since if "content" is of type ContentVersion, getHandle() call would have returned path to the base
702             session.exportSystemView(content.getJCRNode().getPath(), tmpFileOutStream, false, noRecurse);
703             tmpFileOutStream.flush();
704             tmpFileOutStream.close();
705 
706             OutputFormat outputFormat = new OutputFormat();
707             outputFormat.setPreserveSpace(false);
708 
709             tmpFileInStream = new FileInputStream(tempFile);
710             elementfilter.setContentHandler(new XMLSerializer(os, outputFormat));
711             elementfilter.parse(new InputSource(tmpFileInStream));
712             tmpFileInStream.close();
713         } catch (Throwable t) {
714             log.error("Failed to parse XML using FrozenElementFilter",t);
715             throw new Exception(t);
716         } finally {
717             IOUtils.closeQuietly(tmpFileInStream);
718             IOUtils.closeQuietly(tmpFileOutStream);
719             tempFile.delete();
720         }
721     }
722 
723     /**
724      * Gets target path to which the current path is mapped in given subscription. Provided path should be without trailing slash.
725      */
726     protected String getMappedPath(String path, Subscription subscription) {
727         String toURI = subscription.getToURI();
728         if (null != toURI) {
729             String fromURI = subscription.getFromURI();
730             // remove trailing slash if any
731             fromURI = StringUtils.removeEnd(fromURI, "/");
732             toURI = StringUtils.removeEnd(toURI, "/");
733             // apply path transformation if any
734             path = path.replaceFirst(fromURI, toURI);
735             if (path.equals("")) {
736                 path = "/";
737             }
738         }
739         return path;
740     }
741 
742     protected URLConnection prepareConnection(Subscriber subscriber, String urlString) throws ExchangeException {
743 
744         //String handle = getActivationURL(subscriber);
745 
746         try {
747             String authMethod = subscriber.getAuthenticationMethod();
748             // authentication headers
749             if (authMethod != null && "form".equalsIgnoreCase(authMethod)) {
750                 urlString += (urlString.indexOf('?') > 0 ? "&" : "?") + AUTH_USER + "=" + this.user.getName();
751                 urlString += "&" + AUTH_CREDENTIALS + "=" + this.user.getPassword();
752             }
753             URL url = new URL(urlString);
754             URLConnection urlConnection = url.openConnection();
755             urlConnection.setConnectTimeout(subscriber.getConnectTimeout());
756             urlConnection.setReadTimeout(subscriber.getReadTimeout());
757             // authentication headers
758             if (authMethod == null || "basic".equalsIgnoreCase(authMethod)) {
759                 urlConnection.setRequestProperty(AUTHORIZATION, this.basicCredentials);
760             } else if (!"form".equalsIgnoreCase(subscriber.getAuthenticationMethod())) {
761                 log.info("Unknown Authentication method for deactivation: " + subscriber.getAuthenticationMethod());
762             }
763 
764             return urlConnection;
765         } catch (MalformedURLException e) {
766             throw new ExchangeException("Incorrect URL for subscriber " + subscriber + "[" + urlString + "]");
767         } catch (IOException e) {
768             throw new ExchangeException("Not able to send the activation request [" + urlString + "]: " + e.getMessage());
769         } catch (Exception e) {
770             throw new ExchangeException(e);
771         }
772     }
773 
774 
775 }