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