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
374 InputStream documentInputStream = new TeeInputStream(resourceDocument.getStream(), md5);
375 org.jdom.Document jdomDocument = builder.build(documentInputStream);
376 IOUtils.closeQuietly(documentInputStream);
377
378 String sign = SecurityUtil.getMD5Hex(md5.toByteArray());
379 if (!resourcesmd5.equals(sign)) {
380 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.");
381 }
382
383 return jdomDocument.getRootElement();
384 }
385
386 protected void handleChildren(HttpServletRequest request, Content content) {
387 String ruleString = request.getHeader(BaseSyndicatorImpl.CONTENT_FILTER_RULE);
388 Rule rule = new Rule(ruleString, ",");
389 RuleBasedContentFilter filter = new RuleBasedContentFilter(rule);
390
391 this.removeChildren(content, filter);
392 }
393
394 protected String handleMovedContent(String newParentPath, HierarchyManager hm, Element topContentElement, Content content) throws RepositoryException {
395 String currentParentPath = content.getHandle();
396 currentParentPath = currentParentPath.substring(0, currentParentPath.lastIndexOf('/'));
397 String newName = topContentElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_NAME_ATTRIBUTE);
398 if (!newParentPath.endsWith("/")) {
399 newParentPath += "/";
400 }
401 if (!currentParentPath.endsWith("/")) {
402 currentParentPath += "/";
403 }
404 if (!newParentPath.equals(currentParentPath) || !content.getName().equals(newName)) {
405 log.info("Moving content from {} to {} due to activation request.", new Object[] { content.getHandle(), newParentPath + newName});
406 hm.moveTo(content.getHandle(), newParentPath + newName);
407 }
408 return newParentPath;
409 }
410
411 protected String orderImportedNode(String newParentPath, HierarchyManager hm, Element rootElement, Element topContentElement) throws RepositoryException {
412 String name;
413
414 name = topContentElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_NAME_ATTRIBUTE);
415 Content parent = hm.getContent(newParentPath);
416 List siblings = rootElement.getChild(BaseSyndicatorImpl.SIBLINGS_ROOT_ELEMENT).getChildren(BaseSyndicatorImpl.SIBLINGS_ELEMENT);
417 Iterator siblingsIterator = siblings.iterator();
418 while (siblingsIterator.hasNext()) {
419 Element sibling = (Element) siblingsIterator.next();
420
421 try {
422 String siblingUUID = sibling.getAttributeValue(BaseSyndicatorImpl.SIBLING_UUID);
423 Content beforeContent = hm.getContentByUUID(siblingUUID);
424 log.debug("Ordering {} before {}", name, beforeContent.getName());
425 order(parent, name, beforeContent.getName());
426 break;
427 } catch (ItemNotFoundException e) {
428
429 } catch (RepositoryException re) {
430 if (log.isDebugEnabled()) {
431 log.debug("Failed to order node", re);
432 } else {
433 log.warn("Failed to order node");
434 }
435 }
436 }
437
438
439 if (siblings.isEmpty()) {
440 order(parent, name, null);
441 }
442 return name;
443 }
444
445
446 protected void order(Content parent, String name, String orderBefore) throws RepositoryException {
447 try {
448 parent.orderBefore(name, orderBefore);
449 } catch (UnsupportedRepositoryOperationException e) {
450
451 log.warn("Failed to order unorderable content {} at {} due to {}", new Object[] {name, parent.getHandle(), e.getMessage()});
452 }
453 parent.save();
454 }
455
456
457
458
459
460
461 protected synchronized void copyProperties(Content source, Content destination) throws RepositoryException {
462
463
464 Iterator nodeDataIterator = destination.getNodeDataCollection().iterator();
465 while (nodeDataIterator.hasNext()) {
466 NodeData nodeData = (NodeData) nodeDataIterator.next();
467
468
469 if (nodeData.getType() != PropertyType.BINARY) {
470 nodeData.delete();
471 }
472 }
473
474
475 Node destinationNode = destination.getJCRNode();
476 nodeDataIterator = source.getNodeDataCollection().iterator();
477 while (nodeDataIterator.hasNext()) {
478 NodeData nodeData = (NodeData) nodeDataIterator.next();
479 Property property = nodeData.getJCRProperty();
480 if (property.getDefinition().isMultiple()) {
481 if (destination.isGranted(Permission.WRITE)) {
482 destinationNode.setProperty(nodeData.getName(), property.getValues());
483 }
484 else {
485 throw new AccessDeniedException("User not allowed to " + Permission.PERMISSION_NAME_WRITE + " at [" + nodeData.getHandle() + "]");
486 }
487 }
488 else {
489 destination.createNodeData(nodeData.getName(), nodeData.getValue());
490 }
491 }
492 }
493
494
495
496
497
498
499 protected synchronized void removeChildren(Content content, Content.ContentFilter filter) {
500 Iterator children = content.getChildren(filter).iterator();
501
502
503 while (children.hasNext()) {
504 Content child = (Content) children.next();
505 try {
506 child.delete();
507 }
508 catch (Exception e) {
509 log.error("Failed to remove " + child.getHandle() + " | " + e.getMessage());
510 }
511 }
512 }
513
514
515
516
517
518
519
520
521
522
523 protected synchronized void importFresh(Element topContentElement, MultipartForm data, HierarchyManager hierarchyManager, String parentPath) throws ExchangeException, RepositoryException {
524
525
526 String path = parentPath + (parentPath.endsWith("/") ? "" : "/") + topContentElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_NAME_ATTRIBUTE);
527 if (hierarchyManager.isExist(path)) {
528 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);
529 hierarchyManager.delete(path);
530 }
531 try {
532 importResource(data, topContentElement, hierarchyManager, parentPath);
533 hierarchyManager.save();
534 } catch (PathNotFoundException e) {
535 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";
536
537 log.debug(message, e);
538 hierarchyManager.refresh(false);
539 throw new ExchangeException(message);
540 } catch (Exception e) {
541 final String message = "Activation failed | " + e.getMessage();
542 log.error("Exception caught", e);
543 hierarchyManager.refresh(false);
544 throw new ExchangeException(message);
545 }
546 }
547
548
549
550
551
552
553
554
555
556
557 protected synchronized void importOnExisting(Element topContentElement, MultipartForm data,
558 final HierarchyManager hierarchyManager, Content existingContent) throws ExchangeException, RepositoryException {
559 final Iterator<Content> fileListIterator = topContentElement.getChildren(BaseSyndicatorImpl.RESOURCE_MAPPING_FILE_ELEMENT).iterator();
560 final String uuid = UUIDGenerator.getInstance().generateTimeBasedUUID().toString();
561 final String handle = existingContent.getHandle();
562
563 final HierarchyManager systemHM = MgnlContext.getSystemContext().getHierarchyManager(SYSTEM_REPO);
564 try {
565 while (fileListIterator.hasNext()) {
566 Element fileElement = (Element) fileListIterator.next();
567 importResource(data, fileElement, hierarchyManager, handle);
568 }
569
570 Content activationTmp = ContentUtil.getOrCreateContent(systemHM.getRoot(), "activation-tmp", ItemType.FOLDER, true);
571 final Content transientNode = activationTmp.createContent(uuid, MgnlNodeType.NT_PAGE);
572 final String transientStoreHandle = transientNode.getHandle();
573
574 final ByteArrayOutputStream md5 = new ByteArrayOutputStream();
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
584 if (!calculatedMD5.equals(expectedMD5)) {
585 throw new SecurityException(fileName + " signature is not valid. Resource might have been modified in transit.");
586 }
587
588 Content tmpContent = transientNode.getContent(topContentElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_NAME_ATTRIBUTE));
589 copyProperties(tmpContent, existingContent);
590 systemHM.delete(transientStoreHandle);
591 hierarchyManager.save();
592 systemHM.save();
593 } catch (Exception e) {
594
595 hierarchyManager.refresh(false);
596 systemHM.refresh(false);
597
598 log.error("Exception caught", e);
599 throw new ExchangeException("Activation failed : " + e.getMessage());
600 }
601 }
602
603
604
605
606
607
608
609
610
611 protected synchronized void importResource(MultipartForm data, Element resourceElement, HierarchyManager hm, String parentPath) throws Exception {
612
613
614 PermissionUtil.isGranted(hm.getWorkspace().getSession(), parentPath, Session.ACTION_ADD_NODE);
615
616 final String name = resourceElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_NAME_ATTRIBUTE);
617 final String fileName = resourceElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_ID_ATTRIBUTE);
618 final String expectedMD5 = resourceElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_MD_ATTRIBUTE);
619
620 ByteArrayOutputStream md5 = new ByteArrayOutputStream();
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 if (!calculatedMD5.equals(expectedMD5)) {
627 throw new SecurityException(fileName + " signature is not valid. Resource might have been modified in transit. Expected signature:" + expectedMD5 + ", actual signature found: " + calculatedMD5);
628 }
629 Iterator fileListIterator = resourceElement.getChildren(BaseSyndicatorImpl.RESOURCE_MAPPING_FILE_ELEMENT).iterator();
630
631 try {
632 parentPath = hm.getContentByUUID(resourceElement.getAttributeValue(BaseSyndicatorImpl.RESOURCE_MAPPING_UUID_ATTRIBUTE)).getHandle();
633 } catch (ItemNotFoundException e) {
634
635
636 parentPath = StringUtils.removeEnd(parentPath, "/") + "/" + name;
637 }
638 while (fileListIterator.hasNext()) {
639 Element fileElement = (Element) fileListIterator.next();
640 importResource(data, fileElement, hm, parentPath);
641 }
642 }
643
644
645
646
647
648
649 protected synchronized String remove(HttpServletRequest request, String md5) throws Exception {
650
651 if (!md5.equals(SecurityUtil.getMD5Hex(request.getHeader(BaseSyndicatorImpl.NODE_UUID)))) {
652 throw new SecurityException("Signature of resource doesn't match. This seems like malicious attempt to delete content. Request was issued from " + request.getRemoteAddr());
653 }
654 HierarchyManager hm = getHierarchyManager(request);
655 String handle = null;
656 try {
657 Content node = this.getNode(request);
658 handle = node.getHandle();
659 hm.delete(handle);
660 hm.save();
661 } catch (ItemNotFoundException e) {
662 log.debug("Unable to delete node", e);
663 }
664 return handle;
665 }
666
667
668
669
670
671
672 protected HierarchyManager getHierarchyManager(HttpServletRequest request) throws ExchangeException {
673 String workspaceName = request.getHeader(BaseSyndicatorImpl.WORKSPACE_NAME);
674
675 if (StringUtils.isEmpty(workspaceName)) {
676 throw new ExchangeException("Repository or workspace name not sent, unable to activate. Workspace: " + workspaceName) ;
677 }
678 SystemContext sysCtx = MgnlContext.getSystemContext();
679 return sysCtx.getHierarchyManager(workspaceName);
680 }
681
682
683
684
685
686
687
688 protected void cleanUp(HttpServletRequest request, String status) {
689 if (!BaseSyndicatorImpl.DEACTIVATE.equalsIgnoreCase(request.getHeader(BaseSyndicatorImpl.ACTION))) {
690 MultipartForm data = MgnlContext.getPostedForm();
691 if (null != data) {
692 Iterator keys = data.getDocuments().keySet().iterator();
693 while (keys.hasNext()) {
694 String key = (String) keys.next();
695 data.getDocument(key).delete();
696 }
697 }
698 try {
699 final String parentPath = getParentPath(request);
700 if (StringUtils.isEmpty(parentPath) || this.getHierarchyManager(request).isExist(parentPath)) {
701 try {
702 Content content = this.getNode(request);
703 if (content.isLocked()) {
704 content.unlock();
705 }
706 }catch (ItemNotFoundException e) {
707
708 }
709 }
710 } catch (LockException le) {
711
712 log.debug(le.getMessage(), le);
713 } catch (RepositoryException re) {
714
715 log.warn("Exception caught", re);
716 } catch (ExchangeException e) {
717
718 log.warn("Exception caught", e);
719 }
720 }
721
722
723 try {
724 HttpSession httpSession = request.getSession(false);
725 if (httpSession != null) {
726 httpSession.invalidate();
727 }
728 } catch (Throwable t) {
729
730 log.error("failed to invalidate session", t);
731 }
732 }
733
734
735
736
737
738 protected void applyLock(HttpServletRequest request) throws ExchangeException {
739 try {
740 Content parent = waitForLock(request);
741 if (parent != null) {
742
743 parent.lock(true, true);
744 }
745 } catch (LockException le) {
746
747 log.debug(le.getMessage());
748 } catch (RepositoryException re) {
749
750 log.warn("Exception caught", re);
751 }
752 }
753
754
755
756
757
758
759
760
761
762 protected Content waitForLock(HttpServletRequest request) throws ExchangeException, RepositoryException {
763 int retries = getUnlockRetries();
764 long retryWait = getRetryWait() * 1000;
765 Content content = null;
766 try {
767 content = this.getNode(request);
768 while (content.isLocked() && retries > 0) {
769 log.info("Content " + content.getHandle() + " is locked. Will retry " + retries + " more times.");
770 try {
771 Thread.sleep(retryWait);
772 } catch (InterruptedException e) {
773
774 Thread.currentThread().interrupt();
775 }
776 retries--;
777 content = this.getNode(request);
778 }
779 if (content.isLocked()) {
780 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.");
781 }
782 } catch (ItemNotFoundException e) {
783
784 log.debug("Attempt to lock non existing content {} during (de)activation.", getUUID(request));
785 } catch (PathNotFoundException e) {
786
787 log.debug("Attempt to lock non existing content {}:{} during (de)activation.",getHierarchyManager(request).getName(), getParentPath(request));
788 }
789 return content;
790 }
791
792 protected Content getNode(HttpServletRequest request) throws ExchangeException, RepositoryException {
793 if (request.getHeader(BaseSyndicatorImpl.PARENT_PATH) != null) {
794 String parentPath = this.getParentPath(request);
795 log.debug("parent path:" + parentPath);
796 return this.getHierarchyManager(request).getContent(parentPath);
797 } else if (!StringUtils.isEmpty(getUUID(request))){
798 log.debug("node uuid:" + request.getHeader(BaseSyndicatorImpl.NODE_UUID));
799 return this.getHierarchyManager(request).getContentByUUID(request.getHeader(BaseSyndicatorImpl.NODE_UUID));
800 } else {
801 throw new ExchangeException("Request is missing mandatory content identifier.");
802 }
803 }
804
805 protected String getParentPath(HttpServletRequest request) {
806 String parentPath = request.getHeader(BaseSyndicatorImpl.PARENT_PATH);
807 if (StringUtils.isNotEmpty(parentPath)) {
808 return parentPath;
809 }
810 return "";
811 }
812
813 protected String getUUID(HttpServletRequest request) {
814 String parentPath = request.getHeader(BaseSyndicatorImpl.NODE_UUID);
815 if (StringUtils.isNotEmpty(parentPath)) {
816 return parentPath;
817 }
818 return "";
819 }
820
821
822 }