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