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