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