1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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.MgnlKeyPair;
47 import info.magnolia.cms.security.Permission;
48 import info.magnolia.cms.security.PermissionUtil;
49 import info.magnolia.cms.security.SecurityUtil;
50 import info.magnolia.cms.util.ContentUtil;
51 import info.magnolia.cms.util.Rule;
52 import info.magnolia.cms.util.RuleBasedContentFilter;
53 import info.magnolia.context.MgnlContext;
54 import info.magnolia.context.SystemContext;
55
56 import java.io.IOException;
57 import java.io.InputStream;
58 import java.security.DigestInputStream;
59 import java.security.InvalidParameterException;
60 import java.security.MessageDigest;
61 import java.security.NoSuchAlgorithmException;
62 import java.util.Iterator;
63 import java.util.List;
64 import java.util.zip.GZIPInputStream;
65
66 import javax.jcr.ImportUUIDBehavior;
67 import javax.jcr.ItemNotFoundException;
68 import javax.jcr.Node;
69 import javax.jcr.PathNotFoundException;
70 import javax.jcr.Property;
71 import javax.jcr.PropertyType;
72 import javax.jcr.RepositoryException;
73 import javax.jcr.Session;
74 import javax.jcr.UnsupportedRepositoryOperationException;
75 import javax.jcr.lock.LockException;
76 import javax.servlet.FilterChain;
77 import javax.servlet.ServletException;
78 import javax.servlet.http.HttpServletRequest;
79 import javax.servlet.http.HttpServletResponse;
80 import javax.servlet.http.HttpSession;
81
82 import org.apache.commons.io.IOUtils;
83 import org.apache.commons.lang.StringUtils;
84 import org.jdom.Element;
85 import org.jdom.JDOMException;
86 import org.jdom.input.SAXBuilder;
87 import org.safehaus.uuid.UUIDGenerator;
88 import org.slf4j.Logger;
89 import org.slf4j.LoggerFactory;
90
91 import com.google.inject.Inject;
92
93
94
95
96
97
98
99 public class ReceiveFilter extends AbstractMgnlFilter {
100
101 private static final Logger log = LoggerFactory.getLogger(ReceiveFilter.class);
102
103 private int unlockRetries = 10;
104
105 private int retryWait = 2;
106
107 private final ExchangeSimpleModule module;
108 private final MessageDigest md;
109
110
111 @Inject
112 public ReceiveFilter(ExchangeSimpleModule module) {
113 this.module = module;
114 try {
115 md = MessageDigest.getInstance("MD5");
116 } catch (NoSuchAlgorithmException e) {
117 throw new SecurityException("In order to proceed with activation please run Magnolia CMS using Java version with MD5 support.", e);
118 }
119 }
120
121 public int getUnlockRetries() {
122 return unlockRetries;
123 }
124
125 public void setUnlockRetries(int unlockRetries) {
126 this.unlockRetries = unlockRetries;
127 }
128
129 public long getRetryWait() {
130 return retryWait;
131 }
132
133 public void setRetryWait(int retryWait) {
134 this.retryWait = retryWait;
135 }
136
137 @Override
138 public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
139 String statusMessage = "";
140 String status = "";
141 String result = null;
142 try {
143 final String utf8AuthorStatus = request.getHeader(BaseSyndicatorImpl.UTF8_STATUS);
144
145 if (utf8AuthorStatus != null && (Boolean.parseBoolean(utf8AuthorStatus) != SystemProperty.getBooleanProperty(SystemProperty.MAGNOLIA_UTF8_ENABLED))) {
146 throw new UnsupportedOperationException("Activation between instances with different UTF-8 setting is not supported.");
147 }
148 final String action = request.getHeader(BaseSyndicatorImpl.ACTION);
149 if (action == null) {
150 throw new InvalidParameterException("Activation action must be set for each activation request.");
151 }
152
153
154 if (!isAuthorAuthenticated(request, response)) {
155 status = BaseSyndicatorImpl.ACTIVATION_HANDSHAKE;
156 } else {
157
158 applyLock(request);
159 result = receive(request);
160 status = BaseSyndicatorImpl.ACTIVATION_SUCCESSFUL;
161 }
162 }
163 catch (OutOfMemoryError e) {
164 Runtime rt = Runtime.getRuntime();
165 log.error("---------\nOutOfMemoryError caught during activation. Total memory = "
166 + rt.totalMemory()
167 + ", free memory = "
168 + rt.freeMemory()
169 + "\n---------");
170 statusMessage = e.getMessage();
171 status = BaseSyndicatorImpl.ACTIVATION_FAILED;
172 }
173 catch (PathNotFoundException e) {
174
175 log.error(e.getMessage(), e);
176 statusMessage = "Parent not found (not yet activated): " + e.getMessage();
177 status = BaseSyndicatorImpl.ACTIVATION_FAILED;
178 } catch (ExchangeException e) {
179 log.debug(e.getMessage(), e);
180 statusMessage = e.getMessage();
181 status = BaseSyndicatorImpl.ACTIVATION_FAILED;
182 } catch (Throwable e) {
183 log.error(e.getMessage(), e);
184
185 statusMessage = StringUtils.defaultIfEmpty(e.getMessage(), e.getClass().getSimpleName());
186 status = BaseSyndicatorImpl.ACTIVATION_FAILED;
187 }
188 finally {
189 cleanUp(request);
190 setResponseHeaders(response, statusMessage, status, result);
191 }
192 }
193
194 protected boolean isAuthorAuthenticated(HttpServletRequest request, HttpServletResponse response) throws NoSuchAlgorithmException, ExchangeException {
195 if (SecurityUtil.getPublicKey() == null) {
196 if (module.getTempKeys() == null) {
197
198 MgnlKeyPair tempKeys = SecurityUtil.generateKeyPair(module.getActivationKeyLength());
199
200 response.addHeader(BaseSyndicatorImpl.ACTIVATION_AUTH, tempKeys.getPublicKey());
201 module.setTempKeys(tempKeys);
202 return false;
203 } else {
204 try {
205
206 String authorsPublicKeyEncryptedByTempPublicKey = request.getHeader(BaseSyndicatorImpl.ACTIVATION_AUTH_KEY);
207
208 String publicKey = SecurityUtil.decrypt(authorsPublicKeyEncryptedByTempPublicKey, module.getTempKeys().getPrivateKey());
209 if (StringUtils.isNotBlank(publicKey)) {
210 String authString = SecurityUtil.decrypt(request.getHeader(BaseSyndicatorImpl.ACTIVATION_AUTH), publicKey);
211 String[] auth = authString.split(";");
212 checkTimestamp(auth);
213
214
215 SecurityUtil.updateKeys(new MgnlKeyPair(null, publicKey));
216 }
217 } finally {
218
219 module.setTempKeys(null);
220 }
221 if (SecurityUtil.getPublicKey() == null) {
222
223 try {
224 Thread.sleep(3000);
225 } catch (InterruptedException e) {
226 Thread.currentThread().interrupt();
227 }
228 if (SecurityUtil.getPublicKey() == null) {
229 throw new ExchangeException("Failed to negotiate encryption key between author and public instance. Please try again later or contact admin if error persists.");
230 }
231 }
232 }
233 }
234 return true;
235 }
236
237 protected void setResponseHeaders(HttpServletResponse response, String statusMessage, String status, String result) {
238 response.setHeader(BaseSyndicatorImpl.ACTIVATION_ATTRIBUTE_STATUS, status);
239 response.setHeader(BaseSyndicatorImpl.ACTIVATION_ATTRIBUTE_MESSAGE, statusMessage);
240 }
241
242
243
244
245
246
247 protected synchronized String receive(HttpServletRequest request) throws Exception {
248 String action = request.getHeader(BaseSyndicatorImpl.ACTION);
249 log.debug("action: " + action);
250
251 String[] auth = checkAuthenticated(request);
252
253 String user = auth[1];
254
255 String resourcesmd5 = auth[2];
256
257
258 String webapp = getWebappName();
259
260 if (action.equalsIgnoreCase(BaseSyndicatorImpl.ACTIVATE)) {
261 String name = update(request, resourcesmd5);
262
263 log.info("User {} successfuly activated {} on {}.", new Object[] { user, name, webapp });
264 }
265 else if (action.equalsIgnoreCase(BaseSyndicatorImpl.DEACTIVATE)) {
266 String name = remove(request, resourcesmd5);
267
268 log.info("User {} succeessfuly deactivated {} on {}.", new Object[] { user, name, webapp });
269 }
270 else {
271 throw new UnsupportedOperationException("Method not supported : " + action);
272 }
273 return null;
274 }
275
276 protected String[] checkAuthenticated(HttpServletRequest request) throws ExchangeException {
277 String encrypted = request.getHeader(BaseSyndicatorImpl.ACTIVATION_AUTH);
278 if (StringUtils.isBlank(encrypted)) {
279 log.debug("Attempt to access activation URL w/o proper information in request. Ignoring silently.");
280 throw new ExchangeException();
281 }
282
283 String decrypted = SecurityUtil.decrypt(encrypted);
284 if (StringUtils.isBlank(decrypted)) {
285 throw new SecurityException("Handshake information for activation was incorrect. Someone attempted to impersonate author instance. Incoming request was from " + request.getRemoteAddr());
286 }
287
288 String[] auth = decrypted.split(";");
289
290
291 if (auth.length != 3) {
292 throw new SecurityException("Handshake information for activation was incorrect. Someone attempted to impersonate author instance. Incoming request was from " + request.getRemoteAddr());
293 }
294
295 checkTimestamp(auth);
296 return auth;
297 }
298
299 private void checkTimestamp(String[] auth) {
300 long timestamp = System.currentTimeMillis();
301 long authorTimestamp = 0;
302 try {
303 authorTimestamp = Long.parseLong(auth[0]);
304 } catch (NumberFormatException e) {
305 throw new SecurityException("Handshake information for activation was incorrect. This might be an attempt to replay earlier activation request.");
306 }
307 if (Math.abs(timestamp - authorTimestamp) > module.getActivationDelayTolerance()) {
308 throw new SecurityException("Activation refused due to request arriving too late or time not synched between author and public instance. Please contact your administrator to ensure server times are synced or the tolerance is set high enough to counter the differences.");
309 }
310 }
311
312 protected String getWebappName() {
313 return SystemProperty.getProperty(SystemProperty.MAGNOLIA_WEBAPP);
314 }
315
316
317
318
319 @Deprecated
320 protected String getUser(HttpServletRequest request) {
321 return null;
322 }
323
324
325
326
327
328
329
330
331
332
333
334 protected synchronized String update(HttpServletRequest request, String resourcesmd5) throws Exception {
335 MultipartForm data = MgnlContext.getPostedForm();
336 if (null != data) {
337 String newParentPath = this.getParentPath(request);
338 String resourceFileName = request.getHeader(BaseSyndicatorImpl.RESOURCE_MAPPING_FILE);
339 HierarchyManager hm = getHierarchyManager(request);
340 Element rootElement = getImportedContentRoot(data, resourceFileName, resourcesmd5);
341 Element topContentElement = rootElement.getChild(BaseSyndicatorImpl.RESOURCE_MAPPING_FILE_ELEMENT);
342 Content content = null;
343 try {
344 String uuid = topContentElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_UUID_ATTRIBUTE);
345 content = hm.getContentByUUID(uuid);
346
347 newParentPath = handleMovedContent(newParentPath, hm, topContentElement, content);
348 handleChildren(request, content);
349 this.importOnExisting(topContentElement, data, hm, content);
350 }
351 catch (ItemNotFoundException e) {
352
353 importFresh(topContentElement, data, hm, newParentPath);
354 }
355
356 return orderImportedNode(newParentPath, hm, rootElement, topContentElement);
357 }
358 return null;
359 }
360
361 protected Element getImportedContentRoot(MultipartForm data, String resourceFileName, String resourcesmd5) throws JDOMException, IOException {
362 Document resourceDocument = data.getDocument(resourceFileName);
363 SAXBuilder builder = new SAXBuilder();
364 InputStream documentInputStream = new DigestInputStream(resourceDocument.getStream(), md);
365 org.jdom.Document jdomDocument = builder.build(documentInputStream);
366 IOUtils.closeQuietly(documentInputStream);
367 String sign = SecurityUtil.byteArrayToHex(md.digest());
368 if (!resourcesmd5.equals(sign)) {
369 throw new SecurityException("Signature of received resource (" + sign + ") doesn't match expected signature (" + resourcesmd5 + "). This might mean that the activation operation have been intercepted by a third party and content have been modified during transfer.");
370 }
371
372 return jdomDocument.getRootElement();
373 }
374
375 protected void handleChildren(HttpServletRequest request, Content content) {
376 String ruleString = request.getHeader(BaseSyndicatorImpl.CONTENT_FILTER_RULE);
377 Rule rule = new Rule(ruleString, ",");
378 RuleBasedContentFilter filter = new RuleBasedContentFilter(rule);
379
380 this.removeChildren(content, filter);
381 }
382
383 protected String handleMovedContent(String newParentPath, HierarchyManager hm, Element topContentElement, Content content) throws RepositoryException {
384 String currentParentPath = content.getHandle();
385 currentParentPath = currentParentPath.substring(0, currentParentPath.lastIndexOf('/'));
386 String newName = topContentElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_NAME_ATTRIBUTE);
387 if (!newParentPath.endsWith("/")) {
388 newParentPath += "/";
389 }
390 if (!currentParentPath.endsWith("/")) {
391 currentParentPath += "/";
392 }
393 if (!newParentPath.equals(currentParentPath) || !content.getName().equals(newName)) {
394 log.info("Moving content from {} to {} due to activation request.", new Object[] { content.getHandle(), newParentPath + newName});
395 hm.moveTo(content.getHandle(), newParentPath + newName);
396 }
397 return newParentPath;
398 }
399
400 protected String orderImportedNode(String newParentPath, HierarchyManager hm, Element rootElement, Element topContentElement) throws RepositoryException {
401 String name;
402
403 name = topContentElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_NAME_ATTRIBUTE);
404 Content parent = hm.getContent(newParentPath);
405 List siblings = rootElement.getChild(BaseSyndicatorImpl.SIBLINGS_ROOT_ELEMENT).getChildren(BaseSyndicatorImpl.SIBLINGS_ELEMENT);
406 Iterator siblingsIterator = siblings.iterator();
407 while (siblingsIterator.hasNext()) {
408 Element sibling = (Element) siblingsIterator.next();
409
410 try {
411 String siblingUUID = sibling.getAttributeValue(BaseSyndicatorImpl.SIBLING_UUID);
412 Content beforeContent = hm.getContentByUUID(siblingUUID);
413 log.debug("Ordering {} before {}", name, beforeContent.getName());
414 order(parent, name, beforeContent.getName());
415 break;
416 } catch (ItemNotFoundException e) {
417
418 } catch (RepositoryException re) {
419 if (log.isDebugEnabled()) {
420 log.debug("Failed to order node", re);
421 } else {
422 log.warn("Failed to order node");
423 }
424 }
425 }
426
427
428 if (siblings.isEmpty()) {
429 order(parent, name, null);
430 }
431 return name;
432 }
433
434
435 protected void order(Content parent, String name, String orderBefore) throws RepositoryException {
436 try {
437 parent.orderBefore(name, orderBefore);
438 } catch (UnsupportedRepositoryOperationException e) {
439
440 log.warn("Failed to order unorderable content {} at {} due to {}", new Object[] {name, parent.getHandle(), e.getMessage()});
441 }
442 parent.save();
443 }
444
445
446
447
448
449
450 protected synchronized void copyProperties(Content source, Content destination) throws RepositoryException {
451
452
453 Iterator nodeDataIterator = destination.getNodeDataCollection().iterator();
454 while (nodeDataIterator.hasNext()) {
455 NodeData nodeData = (NodeData) nodeDataIterator.next();
456
457
458 if (nodeData.getType() != PropertyType.BINARY) {
459 nodeData.delete();
460 }
461 }
462
463
464 Node destinationNode = destination.getJCRNode();
465 nodeDataIterator = source.getNodeDataCollection().iterator();
466 while (nodeDataIterator.hasNext()) {
467 NodeData nodeData = (NodeData) nodeDataIterator.next();
468 Property property = nodeData.getJCRProperty();
469 if (property.getDefinition().isMultiple()) {
470 if (destination.isGranted(Permission.WRITE)) {
471 destinationNode.setProperty(nodeData.getName(), property.getValues());
472 }
473 else {
474 throw new AccessDeniedException("User not allowed to " + Permission.PERMISSION_NAME_WRITE + " at [" + nodeData.getHandle() + "]");
475 }
476 }
477 else {
478 destination.createNodeData(nodeData.getName(), nodeData.getValue());
479 }
480 }
481 }
482
483
484
485
486
487
488 protected synchronized void removeChildren(Content content, Content.ContentFilter filter) {
489 Iterator children = content.getChildren(filter).iterator();
490
491
492 while (children.hasNext()) {
493 Content child = (Content) children.next();
494 try {
495 child.delete();
496 }
497 catch (Exception e) {
498 log.error("Failed to remove " + child.getHandle() + " | " + e.getMessage());
499 }
500 }
501 }
502
503
504
505
506
507
508
509
510
511
512 protected synchronized void importFresh(Element topContentElement, MultipartForm data, HierarchyManager hierarchyManager, String parentPath) throws ExchangeException, RepositoryException {
513
514
515 String path = parentPath + (parentPath.endsWith("/") ? "" : "/") + topContentElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_NAME_ATTRIBUTE);
516 if (hierarchyManager.isExist(path)) {
517 log.warn("Replacing {} due to name collision (but different UUIDs.)", path);
518 hierarchyManager.delete(path);
519 }
520 try {
521 importResource(data, topContentElement, hierarchyManager, parentPath);
522 hierarchyManager.save();
523 } catch (PathNotFoundException e) {
524 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";
525
526 log.debug(message, e);
527 hierarchyManager.refresh(false);
528 throw new ExchangeException(message);
529 } catch (Exception e) {
530 final String message = "Activation failed | " + e.getMessage();
531 log.error("Exception caught", e);
532 hierarchyManager.refresh(false);
533 throw new ExchangeException(message);
534 }
535 }
536
537
538
539
540
541
542
543
544
545
546 protected synchronized void importOnExisting(Element topContentElement, MultipartForm data,
547 final HierarchyManager hierarchyManager, Content existingContent) throws ExchangeException, RepositoryException {
548 final Iterator<Content> fileListIterator = topContentElement.getChildren(BaseSyndicatorImpl.RESOURCE_MAPPING_FILE_ELEMENT).iterator();
549 final String uuid = UUIDGenerator.getInstance().generateTimeBasedUUID().toString();
550 final String handle = existingContent.getHandle();
551
552 final HierarchyManager systemHM = MgnlContext.getSystemContext().getHierarchyManager("mgnlSystem");
553 try {
554 while (fileListIterator.hasNext()) {
555 Element fileElement = (Element) fileListIterator.next();
556 importResource(data, fileElement, hierarchyManager, handle);
557 }
558
559 Content activationTmp = ContentUtil.getOrCreateContent(systemHM.getRoot(), "activation-tmp", ItemType.FOLDER, true);
560 final Content transientNode = activationTmp.createContent(uuid, ItemType.CONTENTNODE.toString());
561 final String transientStoreHandle = transientNode.getHandle();
562
563 final String fileName = topContentElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_ID_ATTRIBUTE);
564 final String md5 = topContentElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_MD_ATTRIBUTE);
565 final InputStream inputStream = new DigestInputStream(new GZIPInputStream(data.getDocument(fileName).getStream()), md);
566
567 systemHM.getWorkspace().getSession().importXML(transientStoreHandle, inputStream, ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW);
568 IOUtils.closeQuietly(inputStream);
569 final String calculatedMD5 = SecurityUtil.byteArrayToHex(md.digest());
570 if (!calculatedMD5.equals(md5)) {
571 throw new SecurityException(fileName + " signature is not valid. Resource might have been modified in transit.");
572 }
573
574 Content tmpContent = transientNode.getContent(topContentElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_NAME_ATTRIBUTE));
575 copyProperties(tmpContent, existingContent);
576 systemHM.delete(transientStoreHandle);
577 hierarchyManager.save();
578 systemHM.save();
579 } catch (Exception e) {
580
581 hierarchyManager.refresh(false);
582 systemHM.refresh(false);
583
584 log.error("Exception caught", e);
585 throw new ExchangeException("Activation failed : " + e.getMessage());
586 }
587 }
588
589
590
591
592
593
594
595
596
597 protected synchronized void importResource(MultipartForm data, Element resourceElement, HierarchyManager hm, String parentPath) throws Exception {
598
599
600 PermissionUtil.isGranted(hm.getWorkspace().getSession(), parentPath, Session.ACTION_ADD_NODE);
601
602 final String name = resourceElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_NAME_ATTRIBUTE);
603 final String fileName = resourceElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_ID_ATTRIBUTE);
604 final String md5 = resourceElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_MD_ATTRIBUTE);
605
606 final InputStream inputStream = new DigestInputStream(new GZIPInputStream(data.getDocument(fileName).getStream()), md);
607 log.debug("Importing {} into parent path {}", new Object[] {name, parentPath});
608 hm.getWorkspace().getSession().importXML(parentPath, inputStream, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING);
609 IOUtils.closeQuietly(inputStream);
610 final String calculatedMD5 = SecurityUtil.byteArrayToHex(md.digest());
611 if (!calculatedMD5.equals(md5)) {
612 throw new SecurityException(fileName + " signature is not valid. Resource might have been modified in transit. Expected signature:" + md5 + ", actual signature found: " + calculatedMD5);
613 }
614 Iterator fileListIterator = resourceElement.getChildren(BaseSyndicatorImpl.RESOURCE_MAPPING_FILE_ELEMENT).iterator();
615
616 try {
617 parentPath = hm.getContentByUUID(resourceElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_UUID_ATTRIBUTE)).getHandle();
618 } catch (ItemNotFoundException e) {
619
620
621 parentPath = StringUtils.removeEnd(parentPath, "/") + "/" + name;
622 }
623 while (fileListIterator.hasNext()) {
624 Element fileElement = (Element) fileListIterator.next();
625 importResource(data, fileElement, hm, parentPath);
626 }
627 }
628
629
630
631
632
633
634 protected synchronized String remove(HttpServletRequest request, String md5) throws Exception {
635
636 if (!md5.equals(SecurityUtil.byteArrayToHex(md.digest(request.getHeader(BaseSyndicatorImpl.NODE_UUID).getBytes())))) {
637 throw new SecurityException("Signature of resource doesn't match. This seems like malicious attempt to delete content. Request was issued from " + request.getRemoteAddr());
638 }
639 HierarchyManager hm = getHierarchyManager(request);
640 String handle = null;
641 try {
642 Content node = this.getNode(request);
643 handle = node.getHandle();
644 hm.delete(handle);
645 hm.save();
646 } catch (ItemNotFoundException e) {
647 log.debug("Unable to delete node", e);
648 }
649 return handle;
650 }
651
652
653
654
655
656
657 protected HierarchyManager getHierarchyManager(HttpServletRequest request) throws ExchangeException {
658 String workspaceName = request.getHeader(BaseSyndicatorImpl.WORKSPACE_NAME);
659
660 if (StringUtils.isEmpty(workspaceName)) {
661 throw new ExchangeException("Repository or workspace name not sent, unable to activate. Workspace: " + workspaceName) ;
662 }
663 SystemContext sysCtx = MgnlContext.getSystemContext();
664 return sysCtx.getHierarchyManager(workspaceName);
665 }
666
667
668
669
670
671 protected void cleanUp(HttpServletRequest request) {
672 if (BaseSyndicatorImpl.ACTIVATE.equalsIgnoreCase(request.getHeader(BaseSyndicatorImpl.ACTION))) {
673 MultipartForm data = MgnlContext.getPostedForm();
674 if (null != data) {
675 Iterator keys = data.getDocuments().keySet().iterator();
676 while (keys.hasNext()) {
677 String key = (String) keys.next();
678 data.getDocument(key).delete();
679 }
680 }
681 try {
682 final String parentPath = getParentPath(request);
683 if (StringUtils.isEmpty(parentPath) || this.getHierarchyManager(request).isExist(parentPath)) {
684 Content content = this.getNode(request);
685 if (content.isLocked()) {
686 content.unlock();
687 }
688 }
689 } catch (LockException le) {
690
691 log.debug(le.getMessage(), le);
692 } catch (RepositoryException re) {
693
694 log.warn("Exception caught", re);
695 } catch (ExchangeException e) {
696
697 log.warn("Exception caught", e);
698 }
699 }
700
701
702 try {
703 HttpSession httpSession = request.getSession(false);
704 if (httpSession != null) {
705 httpSession.invalidate();
706 }
707 } catch (Throwable t) {
708
709 log.error("failed to invalidate session", t);
710 }
711 }
712
713
714
715
716
717 protected void applyLock(HttpServletRequest request) throws ExchangeException {
718 try {
719 int retries = getUnlockRetries();
720 long retryWait = getRetryWait() * 1000;
721 Content content = this.getNode(request);
722 while (content.isLocked() && retries > 0) {
723 log.info("Content " + content.getHandle() + " is locked. Will retry " + retries + " more times.");
724 try {
725 Thread.sleep(retryWait);
726 } catch (InterruptedException e) {
727
728 Thread.currentThread().interrupt();
729 }
730 retries--;
731 content = this.getNode(request);
732 }
733 if (content.isLocked()) {
734 throw new ExchangeException("Operation not permitted, " + content.getHandle() + " is locked");
735 }
736
737 content.lock(true, true);
738 } catch (LockException le) {
739
740 log.debug(le.getMessage());
741 } catch (ItemNotFoundException e) {
742
743 log.warn("Attempt to lock non existing content {} during (de)activation.",getUUID(request));
744 } catch (PathNotFoundException e) {
745
746 log.debug("Attempt to lock non existing content {}:{} during (de)activation.",getHierarchyManager(request).getName(), getParentPath(request));
747 } catch (RepositoryException re) {
748
749 log.warn("Exception caught", re);
750 }
751 }
752
753 protected Content getNode(HttpServletRequest request) throws ExchangeException, RepositoryException {
754 if (request.getHeader(BaseSyndicatorImpl.PARENT_PATH) != null) {
755 String parentPath = this.getParentPath(request);
756 log.debug("parent path:" + parentPath);
757 return this.getHierarchyManager(request).getContent(parentPath);
758 } else if (!StringUtils.isEmpty(getUUID(request))){
759 log.debug("node uuid:" + request.getHeader(BaseSyndicatorImpl.NODE_UUID));
760 return this.getHierarchyManager(request).getContentByUUID(request.getHeader(BaseSyndicatorImpl.NODE_UUID));
761 } else {
762 throw new ExchangeException("Request is missing mandatory content identifier.");
763 }
764 }
765
766 protected String getParentPath(HttpServletRequest request) {
767 String parentPath = request.getHeader(BaseSyndicatorImpl.PARENT_PATH);
768 if (StringUtils.isNotEmpty(parentPath)) {
769 return parentPath;
770 }
771 return "";
772 }
773
774 protected String getUUID(HttpServletRequest request) {
775 String parentPath = request.getHeader(BaseSyndicatorImpl.NODE_UUID);
776 if (StringUtils.isNotEmpty(parentPath)) {
777 return parentPath;
778 }
779 return "";
780 }
781
782
783 }