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