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