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