View Javadoc

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