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