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