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