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.beans.runtime.Document;
37  import info.magnolia.cms.beans.runtime.MultipartForm;
38  import info.magnolia.cms.core.Access;
39  import info.magnolia.cms.core.Content;
40  import info.magnolia.cms.core.HierarchyManager;
41  import info.magnolia.cms.core.ItemType;
42  import info.magnolia.cms.core.NodeData;
43  import info.magnolia.cms.core.SystemProperty;
44  import info.magnolia.cms.exchange.ExchangeException;
45  import info.magnolia.cms.filters.AbstractMgnlFilter;
46  import info.magnolia.cms.security.AccessDeniedException;
47  import info.magnolia.cms.security.Permission;
48  import info.magnolia.cms.util.ContentUtil;
49  import info.magnolia.cms.util.Rule;
50  import info.magnolia.cms.util.RuleBasedContentFilter;
51  import info.magnolia.context.MgnlContext;
52  import org.apache.commons.codec.binary.Base64;
53  import org.apache.commons.io.IOUtils;
54  import org.apache.commons.lang.StringUtils;
55  import org.jdom.Element;
56  import org.jdom.JDOMException;
57  import org.jdom.input.SAXBuilder;
58  import org.safehaus.uuid.UUIDGenerator;
59  import org.slf4j.Logger;
60  import org.slf4j.LoggerFactory;
61  
62  import javax.jcr.ImportUUIDBehavior;
63  import javax.jcr.ItemNotFoundException;
64  import javax.jcr.Node;
65  import javax.jcr.PathNotFoundException;
66  import javax.jcr.Property;
67  import javax.jcr.PropertyType;
68  import javax.jcr.RepositoryException;
69  import javax.jcr.UnsupportedRepositoryOperationException;
70  import javax.jcr.lock.LockException;
71  import javax.servlet.FilterChain;
72  import javax.servlet.ServletException;
73  import javax.servlet.http.HttpServletRequest;
74  import javax.servlet.http.HttpServletResponse;
75  import javax.servlet.http.HttpSession;
76  import java.io.IOException;
77  import java.io.InputStream;
78  import java.util.Iterator;
79  import java.util.List;
80  import java.util.zip.GZIPInputStream;
81  
82  /**
83   * This filter receives activation requests from another instance and applies them.
84   *
85   * @author Sameer Charles
86   * $Id: ReceiveFilter.java 34324 2010-05-10 10:09:09Z had $
87   */
88  public class ReceiveFilter extends AbstractMgnlFilter {
89  
90      private static final Logger log = LoggerFactory.getLogger(ReceiveFilter.class);
91  
92      /**
93       * @deprecated since 3.5. This is the attribute name that was used in 3.0, so we keep to be able to activate from 3.0.
94       */
95      private static final String SIBLING_UUID_3_0 = "UUID";
96  
97      private int unlockRetries = 10;
98  
99      private int retryWait = 2;
100 
101     public int getUnlockRetries() {
102         return unlockRetries;
103     }
104 
105     public void setUnlockRetries(int unlockRetries) {
106         this.unlockRetries = unlockRetries;
107     }
108 
109     public long getRetryWait() {
110         return retryWait;
111     }
112 
113     public void setRetryWait(int retryWait) {
114         this.retryWait = retryWait;
115     }
116 
117     public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
118         String statusMessage = "";
119         String status = "";
120         String result = null;
121         try {
122             applyLock(request);
123             result = receive(request);
124             status = BaseSyndicatorImpl.ACTIVATION_SUCCESSFUL;
125         }
126         catch (OutOfMemoryError e) {
127             Runtime rt = Runtime.getRuntime();
128             log.error("---------\nOutOfMemoryError caught during activation. Total memory = " //$NON-NLS-1$
129                 + rt.totalMemory()
130                 + ", free memory = " //$NON-NLS-1$
131                 + rt.freeMemory()
132                 + "\n---------"); //$NON-NLS-1$
133             statusMessage = e.getMessage();
134             status = BaseSyndicatorImpl.ACTIVATION_FAILED;
135         }
136         catch (PathNotFoundException e) {
137             // this should not happen. PNFE should be already caught and wrapped in ExchangeEx
138             log.error(e.getMessage(), e);
139             statusMessage = "Parent not found (not yet activated): " + e.getMessage();
140             status = BaseSyndicatorImpl.ACTIVATION_FAILED;
141         } catch (ExchangeException e) {
142             log.debug(e.getMessage(), e);
143             statusMessage = e.getMessage();
144             status = BaseSyndicatorImpl.ACTIVATION_FAILED;
145         } catch (Throwable e) {
146             log.error(e.getMessage(), e);
147             // we can only rely on the exception's actual message to give something back to the user here.
148             statusMessage = StringUtils.defaultIfEmpty(e.getMessage(), e.getClass().getSimpleName());
149             status = BaseSyndicatorImpl.ACTIVATION_FAILED;
150         }
151         finally {
152             cleanUp(request);
153             setResponseHeaders(response, statusMessage, status, result);
154         }
155     }
156 
157     protected void setResponseHeaders(HttpServletResponse response, String statusMessage, String status, String result) {
158         response.setHeader(BaseSyndicatorImpl.ACTIVATION_ATTRIBUTE_STATUS, status);
159         response.setHeader(BaseSyndicatorImpl.ACTIVATION_ATTRIBUTE_MESSAGE, statusMessage);
160     }
161 
162     /**
163       * handle activate or deactivate request.
164       * @param request
165       * @throws Exception if fails to update
166       */
167      protected synchronized String receive(HttpServletRequest request) throws Exception {
168          String action = request.getHeader(BaseSyndicatorImpl.ACTION);
169          log.debug("action: " + action);
170          String authorization = getUser(request);
171          String webapp = getWebappName();
172 
173          if (action.equalsIgnoreCase(BaseSyndicatorImpl.ACTIVATE)) {
174              String name = update(request);
175              // Everything went well
176              log.info("User {} successfuly activated {} on {}.", new Object[]{authorization, name, webapp});
177          }
178          else if (action.equalsIgnoreCase(BaseSyndicatorImpl.DEACTIVATE)) {
179              String name = remove(request);
180              // Everything went well
181              log.info("User {} succeessfuly deactivated {} on {}.", new Object[] {authorization, name, webapp});
182          }
183          else {
184              throw new UnsupportedOperationException("Method not supported : " + action);
185          }
186          return null;
187      }
188 
189     protected String getWebappName() {
190         return SystemProperty.getProperty(SystemProperty.MAGNOLIA_WEBAPP);
191     }
192 
193     protected String getUser(HttpServletRequest request) {
194         // get the user who authorized this request.
195          String user = request.getHeader(BaseSyndicatorImpl.AUTHORIZATION);
196          if (StringUtils.isEmpty(user)) {
197              user = request.getParameter(BaseSyndicatorImpl.AUTH_USER);
198          } else {
199             user = new String(Base64.decodeBase64(user.substring(6).getBytes())); //Basic uname:pwd
200             user = user.substring(0, user.indexOf(":"));
201          }
202         return user;
203     }
204 
205      /**
206       * handle update (activate) request.
207       * @param request
208       * @throws Exception if fails to update
209       */
210      protected synchronized String update(HttpServletRequest request) throws Exception {
211          MultipartForm data = MgnlContext.getPostedForm();
212          if (null != data) {
213              String newParentPath = this.getParentPath(request);
214              String resourceFileName = request.getHeader(BaseSyndicatorImpl.RESOURCE_MAPPING_FILE);
215              HierarchyManager hm = getHierarchyManager(request);
216              Element rootElement = getImportedContentRoot(data, resourceFileName);
217              Element topContentElement = rootElement.getChild(BaseSyndicatorImpl.RESOURCE_MAPPING_FILE_ELEMENT);
218              Content content = null;
219              try {
220                  String uuid = topContentElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_UUID_ATTRIBUTE);
221                  content = hm.getContentByUUID(uuid);
222                  // move content to new location if necessary.
223                  newParentPath = handleMovedContent(newParentPath, hm, topContentElement, content);
224                  handleChildren(request, content);
225                  this.importOnExisting(topContentElement, data, hm, content);
226              }
227              catch (ItemNotFoundException e) {
228                  // new content
229                  importFresh(topContentElement, data, hm, newParentPath);
230              }
231 
232              return orderImportedNode(newParentPath, hm, rootElement, topContentElement);
233          }
234          return null;
235      }
236 
237     protected Element getImportedContentRoot(MultipartForm data, String resourceFileName) throws JDOMException, IOException {
238         Document resourceDocument = data.getDocument(resourceFileName);
239         SAXBuilder builder = new SAXBuilder();
240         InputStream documentInputStream = resourceDocument.getStream();
241         org.jdom.Document jdomDocument = builder.build(documentInputStream);
242         IOUtils.closeQuietly(documentInputStream);
243         return jdomDocument.getRootElement();
244     }
245 
246     protected void handleChildren(HttpServletRequest request, Content content) {
247         String ruleString = request.getHeader(BaseSyndicatorImpl.CONTENT_FILTER_RULE);
248          Rule rule = new Rule(ruleString, ",");
249          RuleBasedContentFilter filter = new RuleBasedContentFilter(rule);
250          // remove all child nodes
251          this.removeChildren(content, filter);
252     }
253 
254     protected String handleMovedContent(String newParentPath,
255             HierarchyManager hm, Element topContentElement, Content content)
256             throws RepositoryException {
257         String currentParentPath = content.getHandle();
258          currentParentPath = currentParentPath.substring(0, currentParentPath.lastIndexOf('/'));
259          String newName = topContentElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_NAME_ATTRIBUTE);
260          if (!newParentPath.endsWith("/")) {
261              newParentPath += "/";
262          }
263          if (!currentParentPath.endsWith("/")) {
264              currentParentPath += "/";
265          }
266          if (!newParentPath.equals(currentParentPath) || !content.getName().equals(newName)) {
267              log.info("Moving content from {} to {} due to activation request.", new Object[] { content.getHandle(), newParentPath  + newName});
268              hm.moveTo(content.getHandle(), newParentPath + newName);
269          }
270         return newParentPath;
271     }
272 
273     protected String orderImportedNode(String newParentPath, HierarchyManager hm, Element rootElement, Element topContentElement) throws RepositoryException {
274         String name;
275         // order imported node
276         name = topContentElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_NAME_ATTRIBUTE);
277         Content parent = hm.getContent(newParentPath);
278         List siblings = rootElement.getChild(BaseSyndicatorImpl.SIBLINGS_ROOT_ELEMENT).getChildren(BaseSyndicatorImpl.SIBLINGS_ELEMENT);
279         Iterator siblingsIterator = siblings.iterator();
280         while (siblingsIterator.hasNext()) {
281             Element sibling = (Element) siblingsIterator.next();
282             // check for existence and order
283             try {
284                 String siblingUUID = sibling.getAttributeValue(BaseSyndicatorImpl.SIBLING_UUID);
285                 // be compatible with 3.0 (MAGNOLIA-2016)
286                 if (StringUtils.isEmpty(siblingUUID)) {
287                     log.debug("Activating from a Magnolia 3.0 instance");
288                     siblingUUID = sibling.getAttributeValue(SIBLING_UUID_3_0);
289                 }
290                 Content beforeContent = hm.getContentByUUID(siblingUUID);
291                 log.debug("Ordering {} before {}", name, beforeContent.getName());
292                 order(parent, name, beforeContent.getName());
293                 break;
294             } catch (ItemNotFoundException e) {
295                 // ignore
296             } catch (RepositoryException re) {
297                 log.warn("Failed to order node");
298                 log.debug("Failed to order node", re);
299             }
300         }
301 
302         // ensure the no sibling nodes are at the end ... since move is not activated immediately it is sometimes necessary to preserve right order
303         if (siblings.isEmpty()) {
304             order(parent, name, null);
305         }
306         return name;
307     }
308 
309 
310     protected void order(Content parent, String name, String orderBefore) throws RepositoryException {
311         try {
312             parent.orderBefore(name, orderBefore);
313         } catch (UnsupportedRepositoryOperationException e) {
314             // since not all types support ordering we should not enforce it, but only log the error
315             log.warn("Failed to order unorderable content {} at {} due to {}", new Object[] {name, parent.getHandle(), e.getMessage()});
316         }
317         parent.save();
318     }
319 
320     /**
321       * Copy all properties from source to destination (by cleaning the old properties).
322       * @param source the content node to be copied
323       * @param destination the destination node
324       */
325      protected synchronized void copyProperties(Content source, Content destination) throws RepositoryException {
326          // first remove all existing properties at the destination
327          // will be different with incremental activation
328          Iterator nodeDataIterator = destination.getNodeDataCollection().iterator();
329          while (nodeDataIterator.hasNext()) {
330              NodeData nodeData = (NodeData) nodeDataIterator.next();
331              // Ignore binary types, since these are sub nodes and already taken care of while
332              // importing sub resources
333              if (nodeData.getType() != PropertyType.BINARY) {
334                  nodeData.delete();
335              }
336          }
337 
338          // copy all properties
339          Node destinationNode = destination.getJCRNode();
340          nodeDataIterator = source.getNodeDataCollection().iterator();
341          while (nodeDataIterator.hasNext()) {
342              NodeData nodeData = (NodeData) nodeDataIterator.next();
343              Property property = nodeData.getJCRProperty();
344              if (property.getDefinition().isMultiple()) {
345                  if (destination.isGranted(Permission.WRITE)) {
346                      destinationNode.setProperty(nodeData.getName(), property.getValues());
347                  }
348                  else {
349                      throw new AccessDeniedException("User not allowed to " + Permission.PERMISSION_NAME_WRITE + " at [" + nodeData.getHandle() + "]"); //$NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$
350                  }
351              }
352              else {
353                  destination.createNodeData(nodeData.getName(), nodeData.getValue());
354              }
355          }
356      }
357 
358      /**
359       * remove children.
360       * @param content whose children to be deleted
361       * @param filter content filter
362       */
363      protected synchronized void removeChildren(Content content, Content.ContentFilter filter) {
364          Iterator children = content.getChildren(filter).iterator();
365          // remove sub nodes using the same filter used by the sender to collect
366          // this will make sure there is no existing nodes of the same type
367          while (children.hasNext()) {
368              Content child = (Content) children.next();
369              try {
370                  child.delete();
371              }
372              catch (Exception e) {
373                  log.error("Failed to remove " + child.getHandle() + " | " + e.getMessage());
374              }
375          }
376      }
377 
378      /**
379       * import on non existing tree.
380       * @param topContentElement
381       * @param data
382       * @param hierarchyManager
383       * @param parentPath
384       * @throws ExchangeException
385       * @throws RepositoryException
386       */
387      protected synchronized void importFresh(Element topContentElement, MultipartForm data, HierarchyManager hierarchyManager, String parentPath) throws ExchangeException, RepositoryException {
388          // content might still exists under different uuid if it was auto generated. Check the path first, if exists, then remove it before activating new content into same path
389          // TODO: handle same name siblings!
390          String path = parentPath + (parentPath.endsWith("/") ? "" : "/") + topContentElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_NAME_ATTRIBUTE);
391          if (hierarchyManager.isExist(path)) {
392              log.warn("Replacing {} due to name collision (but different UUIDs.)", path);
393              hierarchyManager.delete(path);
394          }
395          try {
396              importResource(data, topContentElement, hierarchyManager, parentPath);
397              hierarchyManager.save();
398          } catch (PathNotFoundException e) {
399              final String  message = "Parent content " + parentPath + " is not yet activated or you do not have write access to it. Please activate the parent content before activating children and ensure you have appropriate rights"; // .. on XXX will be appended to the error message by syndicator on the author instance
400              // this is not a system error so there should not be a need to log the exception all the time.
401              log.debug(message, e);
402              hierarchyManager.refresh(false); // revert all transient changes made in this session till now.
403              throw new ExchangeException(message);
404          } catch (Exception e) {
405              final String message = "Activation failed | " + e.getMessage();
406              log.error("Exception caught", e);
407              hierarchyManager.refresh(false); // revert all transient changes made in this session till now.
408              throw new ExchangeException(message);
409          }
410      }
411 
412     /**
413      * import on existing content, making sure that content which is not sent stays as is.
414      * @param topContentElement
415      * @param data
416      * @param hierarchyManager
417      * @param existingContent
418      * @throws ExchangeException
419      * @throws RepositoryException
420      */
421     protected synchronized void importOnExisting(Element topContentElement, MultipartForm data,
422         final HierarchyManager hierarchyManager, Content existingContent) throws ExchangeException, RepositoryException {
423         final Iterator<Content> fileListIterator = topContentElement.getChildren(BaseSyndicatorImpl.RESOURCE_MAPPING_FILE_ELEMENT).iterator();
424         final String uuid = UUIDGenerator.getInstance().generateTimeBasedUUID().toString();
425         final String handle = existingContent.getHandle();
426         // Can't execute in system context here just get hm from SC and use it for temp node handling
427         final HierarchyManager systemHM = MgnlContext.getSystemContext().getHierarchyManager("mgnlSystem");
428         try {
429             while (fileListIterator.hasNext()) {
430                 Element fileElement = (Element) fileListIterator.next();
431                 importResource(data, fileElement, hierarchyManager, handle);
432             }
433             // use temporary node in mgnlSystem workspace to extract the top level node and copy its properties
434             Content activationTmp = ContentUtil.getOrCreateContent(systemHM.getRoot(), "activation-tmp", ItemType.FOLDER, true);
435             final Content transientNode = activationTmp.createContent(uuid, ItemType.CONTENTNODE.toString());
436             final String transientStoreHandle = transientNode.getHandle();
437             // import properties into transientStore
438             final String fileName = topContentElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_ID_ATTRIBUTE);
439             final GZIPInputStream inputStream = new GZIPInputStream(data.getDocument(fileName).getStream());
440             // need to import in system context
441             systemHM.getWorkspace().getSession().importXML(transientStoreHandle, inputStream, ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW);
442             IOUtils.closeQuietly(inputStream);
443             // copy properties from transient store to existing content
444             Content tmpContent = transientNode.getChildByName(topContentElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_NAME_ATTRIBUTE));
445             copyProperties(tmpContent, existingContent);
446             systemHM.delete(transientStoreHandle);
447             hierarchyManager.save();
448             systemHM.save();
449         } catch (Exception e) {
450             // revert all transient changes made in this session till now.
451             hierarchyManager.refresh(false);
452             systemHM.refresh(false);
453 
454             log.error("Exception caught", e);
455             throw new ExchangeException("Activation failed : " + e.getMessage());
456         }
457     }
458 
459      /**
460       * import documents.
461       * @param data as sent
462       * @param resourceElement parent file element
463       * @param hm
464       * @param parentPath Path to the node parent
465       * @throws Exception
466       */
467      protected synchronized void importResource(MultipartForm data, Element resourceElement, HierarchyManager hm, String parentPath) throws Exception {
468 
469          // throws an exception in case you don't have the permission
470          Access.isGranted(hm.getAccessManager(), parentPath, Permission.WRITE);
471 
472          final String name = resourceElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_NAME_ATTRIBUTE);
473          final String fileName = resourceElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_ID_ATTRIBUTE);
474          // do actual import
475          final GZIPInputStream inputStream = new GZIPInputStream(data.getDocument(fileName).getStream());
476          log.debug("Importing {} into parent path {}", new Object[] {name, parentPath});
477          hm.getWorkspace().getSession().importXML(parentPath, inputStream, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING);
478          IOUtils.closeQuietly(inputStream);
479          Iterator fileListIterator = resourceElement.getChildren(BaseSyndicatorImpl.RESOURCE_MAPPING_FILE_ELEMENT).iterator();
480          // parent path
481          try {
482              parentPath = hm.getContentByUUID(resourceElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_UUID_ATTRIBUTE)).getHandle();
483          } catch (ItemNotFoundException e) {
484              // non referencable content like meta data ...
485              // FYI: if we ever have non referencable same name sibling content the trouble will be here with child content being mixed
486              parentPath = StringUtils.removeEnd(parentPath, "/") + "/" + name;
487          }
488          while (fileListIterator.hasNext()) {
489              Element fileElement = (Element) fileListIterator.next();
490              importResource(data, fileElement, hm, parentPath);
491          }
492      }
493 
494      /**
495       * Deletes (de-activate) the content specified by the request.
496       * @param request
497       * @throws Exception if fails to update
498       */
499      protected synchronized String remove(HttpServletRequest request) throws Exception {
500          HierarchyManager hm = getHierarchyManager(request);
501          String handle = null;
502          try {
503              Content node = this.getNode(request);
504              handle = node.getHandle();
505              hm.delete(handle);
506              hm.save();
507          } catch (ItemNotFoundException e) {
508              log.debug("Unable to delete node", e);
509          }
510          return handle;
511      }
512 
513      /**
514       * get hierarchy manager.
515       * @param request
516       * @throws ExchangeException
517       */
518      protected HierarchyManager getHierarchyManager(HttpServletRequest request) throws ExchangeException {
519          String repositoryName = request.getHeader(BaseSyndicatorImpl.REPOSITORY_NAME);
520          String workspaceName = request.getHeader(BaseSyndicatorImpl.WORKSPACE_NAME);
521 
522          if (StringUtils.isEmpty(repositoryName) || StringUtils.isEmpty(workspaceName)) {
523              throw new ExchangeException("Repository or workspace name not sent, unable to activate. Repository: " + repositoryName + ", workspace: " + workspaceName) ;
524          }
525 
526          return MgnlContext.getHierarchyManager(repositoryName, workspaceName);
527      }
528 
529      /**
530       * cleans temporary store and removes any locks set.
531       * @param request
532       */
533      protected void cleanUp(HttpServletRequest request) {
534          if (BaseSyndicatorImpl.ACTIVATE.equalsIgnoreCase(request.getHeader(BaseSyndicatorImpl.ACTION))) {
535              MultipartForm data = MgnlContext.getPostedForm();
536              if (null != data) {
537                  Iterator keys = data.getDocuments().keySet().iterator();
538                  while (keys.hasNext()) {
539                      String key = (String) keys.next();
540                      data.getDocument(key).delete();
541                  }
542              }
543              try {
544                  final String parentPath = getParentPath(request);
545                  if (StringUtils.isEmpty(parentPath) || this.getHierarchyManager(request).isExist(parentPath)) {
546                      Content content = this.getNode(request);
547                      if (content.isLocked()) {
548                          content.unlock();
549                      }
550                  }
551              } catch (LockException le) {
552                  // either repository does not support locking OR this node never locked
553                  log.debug(le.getMessage());
554              } catch (RepositoryException re) {
555                  // should never come here
556                  log.warn("Exception caught", re);
557              } catch (ExchangeException e) {
558                  // should never come here
559                  log.warn("Exception caught", e);
560              }
561          }
562 
563          // TODO : why is this here ? as far as I can tell, http sessions are never created when reaching this
564          try {
565              HttpSession httpSession = request.getSession(false);
566              if (httpSession != null) httpSession.invalidate();
567          } catch (Throwable t) {
568              // its only a test so just dump
569              log.error("failed to invalidate session", t);
570          }
571      }
572 
573      /**
574       * apply lock.
575       * @param request
576       */
577      protected void applyLock(HttpServletRequest request) throws ExchangeException {
578          try {
579              int retries = getUnlockRetries();
580              long retryWait = getRetryWait() * 1000;
581              Content content = this.getNode(request);
582              while (content.isLocked() && retries > 0) {
583                  log.info("Content " + content.getHandle() + " is locked. Will retry " + retries + " more times.");
584                  try {
585                     Thread.sleep(retryWait);
586                 } catch (InterruptedException e) {
587                     // Restore the interrupted status
588                     Thread.currentThread().interrupt();
589                 }
590                  retries--;
591                  content = this.getNode(request);
592              }
593              if (content.isLocked()) {
594                  throw new ExchangeException("Operation not permitted, " + content.getHandle() + " is locked");
595              }
596              // get a new deep lock
597              content.lock(true, true);
598          } catch (LockException le) {
599              // either repository does not support locking OR this node never locked
600              log.debug(le.getMessage());
601          } catch (ItemNotFoundException e) {
602              // - when deleting new piece of content on the author and mgnl tries to deactivate it on public automatically
603              log.warn("Attempt to lock non existing content {} during (de)activation.",getUUID(request));
604          } catch (PathNotFoundException e) {
605              // - when attempting to activate the content for which parent content have not been yet activated
606              log.debug("Attempt to lock non existing content {}:{} during (de)activation.",getHierarchyManager(request).getName(), getParentPath(request));
607          } catch (RepositoryException re) {
608              // will blow fully at later stage
609              log.warn("Exception caught", re);
610          }
611      }
612 
613      protected Content getNode(HttpServletRequest request) throws ExchangeException, RepositoryException {
614          if (request.getHeader(BaseSyndicatorImpl.PARENT_PATH) != null) {
615              String parentPath = this.getParentPath(request);
616              log.debug("parent path:" + parentPath);
617              return this.getHierarchyManager(request).getContent(parentPath);
618          } else if (!StringUtils.isEmpty(getUUID(request))){
619              log.debug("node uuid:" + request.getHeader(BaseSyndicatorImpl.NODE_UUID));
620              return this.getHierarchyManager(request).getContentByUUID(request.getHeader(BaseSyndicatorImpl.NODE_UUID));
621          }
622          // 3.0 protocol
623          else {
624              log.debug("path: {}", request.getHeader(BaseSyndicatorImpl.PATH));
625              return this.getHierarchyManager(request).getContent(request.getHeader(BaseSyndicatorImpl.PATH));
626          }
627      }
628 
629      protected String getParentPath(HttpServletRequest request) {
630          String parentPath = request.getHeader(BaseSyndicatorImpl.PARENT_PATH);
631          if (StringUtils.isNotEmpty(parentPath)) {
632              return parentPath;
633          }
634          return "";
635      }
636 
637      protected String getUUID(HttpServletRequest request) {
638          String parentPath = request.getHeader(BaseSyndicatorImpl.NODE_UUID);
639          if (StringUtils.isNotEmpty(parentPath)) {
640              return parentPath;
641          }
642          return "";
643      }
644 
645 
646 }