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