View Javadoc

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