View Javadoc

1   /**
2    * This file Copyright (c) 2003-2010 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.SystemProperty;
37  import info.magnolia.cms.exchange.ActivationManagerFactory;
38  import info.magnolia.cms.exchange.ExchangeException;
39  import info.magnolia.cms.exchange.Subscriber;
40  import info.magnolia.cms.exchange.Subscription;
41  
42  import java.io.IOException;
43  import java.io.UnsupportedEncodingException;
44  import java.net.HttpURLConnection;
45  import java.net.MalformedURLException;
46  import java.net.URL;
47  import java.net.URLConnection;
48  import java.net.URLEncoder;
49  import java.util.Collection;
50  import java.util.Iterator;
51  import java.util.Map;
52  import java.util.Map.Entry;
53  
54  import org.apache.commons.lang.StringUtils;
55  import org.slf4j.Logger;
56  import org.slf4j.LoggerFactory;
57  
58  import EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap;
59  import EDU.oswego.cs.dl.util.concurrent.CountDown;
60  import EDU.oswego.cs.dl.util.concurrent.Sync;
61  
62  
63  /**
64   * Implementation of syndicator that simply sends all activated content over http connection specified in the subscriber.
65   * @author Sameer Charles $Id: SimpleSyndicator.java 36919 2010-09-03 08:10:46Z had $
66   */
67  public class SimpleSyndicator extends BaseSyndicatorImpl {
68      private static final Logger log = LoggerFactory.getLogger(SimpleSyndicator.class);
69  
70      public SimpleSyndicator() {
71      }
72  
73      public void activate(final ActivationContent activationContent) throws ExchangeException {
74          Collection subscribers = ActivationManagerFactory.getActivationManager().getSubscribers();
75          Iterator subscriberIterator = subscribers.iterator();
76          final Sync done = new CountDown(subscribers.size());
77          final Map errors = new ConcurrentHashMap(subscribers.size());
78          while (subscriberIterator.hasNext()) {
79              final Subscriber subscriber = (Subscriber) subscriberIterator.next();
80              if (subscriber.isActive()) {
81                  // Create runnable task for each subscriber execute
82                  executeInPool(getActivateTask(activationContent, done, errors, subscriber));
83              } else {
84                  // count down directly
85                  done.release();
86              }
87          } //end of subscriber loop
88  
89          // wait until all tasks are executed before returning back to user to make sure errors can be propagated back to the user.
90          acquireIgnoringInterruption(done);
91  
92          String uuid = activationContent.getproperty(NODE_UUID);
93          // collect all the errors and send them back.
94          if (!errors.isEmpty()) {
95              Exception e = null;
96              StringBuffer msg = new StringBuffer(errors.size() + " error").append(errors.size() > 1 ? "s" : "").append(" detected: ");
97              Iterator iter = errors.entrySet().iterator();
98              while (iter.hasNext()) {
99                  Entry entry = (Entry) iter.next();
100                 e = (Exception) entry.getValue();
101                 Subscriber subscriber = (Subscriber) entry.getKey();
102                 msg.append("\n").append(e.getMessage()).append(" on ").append(subscriber.getName());
103                 log.error(e.getMessage(), e);
104             }
105 
106             throw new ExchangeException(msg.toString(), e);
107         }
108 
109         executeInPool(new Runnable() {
110             public void run() {
111                 cleanTemporaryStore(activationContent);
112             }
113         });
114     }
115 
116     private Runnable getActivateTask(final ActivationContent activationContent, final Sync done, final Map errors, final Subscriber subscriber) {
117         Runnable r = new Runnable() {
118             public void run() {
119                 try {
120                     activate(subscriber, activationContent);
121                 } catch (ExchangeException e) {
122                     log.error("Failed to activate content.", e);
123                     errors.put(subscriber,e);
124                 } finally {
125                     done.release();
126                 }
127             }
128         };
129         return r;
130     }
131 
132     /**
133      * Send activation request if subscribed to the activated URI.
134      * @param subscriber
135      * @param activationContent
136      * @throws ExchangeException
137      */
138     public String activate(Subscriber subscriber, ActivationContent activationContent) throws ExchangeException {
139         log.debug("activate");
140         if (null == subscriber) {
141             throw new ExchangeException("Null Subscriber");
142         }
143 
144         String parentPath = null;
145 
146         Subscription subscription = subscriber.getMatchedSubscription(this.path, this.repositoryName);
147         if (null != subscription) {
148             // its subscribed since we found the matching subscription
149             // unfortunately activationContent is not thread safe and is used by multiple threads in case of multiple subscribers so we can't use it as a vessel for transfer of parentPath value
150             parentPath = this.getMappedPath(this.parent, subscription);
151         } else {
152             log.debug("Exchange : subscriber [{}] is not subscribed to {}", subscriber.getName(), this.path);
153             return null;
154         }
155         log.debug("Exchange : sending activation request to {} with user {}", subscriber.getName(), this.user.getName()); //$NON-NLS-1$
156 
157         if(SystemProperty.getBooleanProperty(SystemProperty.MAGNOLIA_UTF8_ENABLED)) {
158             try {
159                 parentPath = URLEncoder.encode(parentPath, "UTF-8");
160             }
161             catch (UnsupportedEncodingException e) {
162                 // do nothing
163             }
164         }
165 
166         URLConnection urlConnection = null;
167         try {
168             urlConnection = prepareConnection(subscriber);
169             this.addActivationHeaders(urlConnection, activationContent);
170             // set a parent path manually instead of via activationHeaders since it can differ between subscribers.
171             urlConnection.setRequestProperty(PARENT_PATH, parentPath);
172 
173             Transporter.transport((HttpURLConnection) urlConnection, activationContent);
174 
175             String status = urlConnection.getHeaderField(ACTIVATION_ATTRIBUTE_STATUS);
176 
177             // check if the activation failed
178             if (StringUtils.equals(status, ACTIVATION_FAILED)) {
179                 String message = urlConnection.getHeaderField(ACTIVATION_ATTRIBUTE_MESSAGE);
180                 throw new ExchangeException("Message received from subscriber: " + message);
181             }
182             urlConnection.getContent();
183             log.debug("Exchange : activation request sent to {}", subscriber.getName()); //$NON-NLS-1$
184         }
185         catch (ExchangeException e) {
186             throw e;
187         }
188         catch (IOException e) {
189             throw new ExchangeException("Not able to send the activation request [" + (urlConnection == null ? null : urlConnection.getURL()) + "]: " + e.getMessage());
190         }
191         catch (Exception e) {
192             throw new ExchangeException(e);
193         }
194         return null;
195     }
196 
197     protected URLConnection prepareConnection(Subscriber subscriber) throws ExchangeException {
198 
199         String handle = getActivationURL(subscriber);
200 
201         try {
202             // authentication headers
203             if (subscriber.getAuthenticationMethod() != null && "form".equalsIgnoreCase(subscriber.getAuthenticationMethod())) {
204                 handle += (handle.indexOf('?') > 0 ? "&" : "?") + AUTH_USER + "=" + this.user.getName();
205                 handle += "&" + AUTH_CREDENTIALS + "=" + this.user.getPassword();
206             }
207             URL url = new URL(handle);
208             URLConnection urlConnection = url.openConnection();
209             // authentication headers
210             if (subscriber.getAuthenticationMethod() == null || "basic".equalsIgnoreCase(subscriber.getAuthenticationMethod())) {
211                 urlConnection.setRequestProperty(AUTHORIZATION, this.basicCredentials);
212             } else if (!"form".equalsIgnoreCase(subscriber.getAuthenticationMethod())) {
213                 log.info("Unknown Authentication method for deactivation: " + subscriber.getAuthenticationMethod());
214             }
215 
216             return urlConnection;
217         } catch (MalformedURLException e) {
218             throw new ExchangeException("Incorrect URL for subscriber " + subscriber + "[" + handle + "]");
219         } catch (IOException e) {
220             throw new ExchangeException("Not able to send the activation request [" + handle + "]: " + e.getMessage());
221         } catch (Exception e) {
222             throw new ExchangeException(e);
223         }
224     }
225 
226     public void doDeactivate() throws ExchangeException {
227         Collection subscribers = ActivationManagerFactory.getActivationManager().getSubscribers();
228         Iterator subscriberIterator = subscribers.iterator();
229         final Sync done = new CountDown(subscribers.size());
230         final Map errors = new ConcurrentHashMap();
231         while (subscriberIterator.hasNext()) {
232             final Subscriber subscriber = (Subscriber) subscriberIterator.next();
233             if (subscriber.isActive()) {
234                 // Create runnable task for each subscriber.
235                 executeInPool(getDeactivateTask(done, errors, subscriber));
236             } else {
237                 // count down directly
238                 done.release();
239             }
240         } //end of subscriber loop
241 
242         // wait until all tasks are executed before returning back to user to make sure errors can be propagated back to the user.
243         acquireIgnoringInterruption(done);
244 
245         // collect all the errors and send them back.
246         if (!errors.isEmpty()) {
247             Exception e = null;
248             StringBuffer msg = new StringBuffer(errors.size() + " error").append(
249             errors.size() > 1 ? "s" : "").append(" detected: ");
250             Iterator iter = errors.entrySet().iterator();
251             while (iter.hasNext()) {
252                 Entry entry = (Entry) iter.next();
253                 e = (Exception) entry.getValue();
254                 Subscriber subscriber = (Subscriber) entry.getKey();
255                 msg.append("\n").append(e.getMessage()).append(" on ").append(subscriber.getName());
256                 log.error(e.getMessage(), e);
257             }
258 
259             throw new ExchangeException(msg.toString(), e);
260         }
261     }
262 
263     private Runnable getDeactivateTask(final Sync done, final Map errors, final Subscriber subscriber) {
264         Runnable r = new Runnable() {
265             public void run() {
266                 try {
267                     doDeactivate(subscriber);
268                 } catch (ExchangeException e) {
269                     log.error("Failed to deactivate content.", e);
270                     errors.put(subscriber,e);
271                 } finally {
272                     done.release();
273                 }
274             }
275         };
276         return r;
277     }
278 
279     /**
280      * Deactivate from a specified subscriber.
281      * @param subscriber
282      * @throws ExchangeException
283      */
284     public String doDeactivate(Subscriber subscriber) throws ExchangeException {
285         Subscription subscription = subscriber.getMatchedSubscription(this.path, this.repositoryName);
286         if (null != subscription) {
287             String handle = getDeactivationURL(subscriber);
288             try {
289                 URLConnection urlConnection = prepareConnection(subscriber);
290 
291                 this.addDeactivationHeaders(urlConnection);
292                 String status = urlConnection.getHeaderField(ACTIVATION_ATTRIBUTE_STATUS);
293 
294                 // check if the activation failed
295                 if (StringUtils.equals(status, ACTIVATION_FAILED)) {
296                     String message = urlConnection.getHeaderField(ACTIVATION_ATTRIBUTE_MESSAGE);
297                     throw new ExchangeException("Message received from subscriber: " + message);
298                 }
299 
300                 urlConnection.getContent();
301 
302             }
303             catch (MalformedURLException e) {
304                 throw new ExchangeException("Incorrect URL for subscriber " + subscriber + "[" + handle + "]");
305             }
306             catch (IOException e) {
307                 throw new ExchangeException("Not able to send the deactivation request [" + handle + "]: " + e.getMessage());
308             }
309             catch (Exception e) {
310                 throw new ExchangeException(e);
311             }
312         }
313         return null;
314     }
315 
316     /**
317      * Gets target path to which the current path is mapped in given subscription. Provided path should be without trailing slash.
318      */
319     protected String getMappedPath(String path, Subscription subscription) {
320         String toURI = subscription.getToURI();
321         if (null != toURI) {
322             String fromURI = subscription.getFromURI();
323             // remove trailing slash if any
324             fromURI = StringUtils.removeEnd(fromURI, "/");
325             toURI = StringUtils.removeEnd(toURI, "/");
326             // apply path transformation if any
327             path = path.replaceFirst(fromURI, toURI);
328             if (path.equals("")) {
329                 return "/";
330             }
331         }
332         return path;
333     }
334 
335 }