View Javadoc

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