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.config.ContentRepository;
37 import info.magnolia.cms.core.Content;
38 import info.magnolia.cms.core.HierarchyManager;
39 import info.magnolia.cms.core.ItemType;
40 import info.magnolia.cms.core.MetaData;
41 import info.magnolia.cms.core.Path;
42 import info.magnolia.cms.core.SystemProperty;
43 import info.magnolia.cms.exchange.ExchangeException;
44 import info.magnolia.cms.exchange.Subscriber;
45 import info.magnolia.cms.exchange.Syndicator;
46 import info.magnolia.cms.security.AccessDeniedException;
47 import info.magnolia.cms.security.User;
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 info.magnolia.logging.AuditLoggingUtil;
53
54 import java.io.File;
55 import java.io.FileInputStream;
56 import java.io.FileOutputStream;
57 import java.io.IOException;
58 import java.io.OutputStream;
59 import java.io.UnsupportedEncodingException;
60 import java.net.URLConnection;
61 import java.net.URLEncoder;
62 import java.util.Iterator;
63 import java.util.List;
64 import java.util.zip.GZIPOutputStream;
65
66 import javax.jcr.RepositoryException;
67 import javax.jcr.Session;
68
69 import org.apache.commons.codec.binary.Base64;
70 import org.apache.commons.io.IOUtils;
71 import org.apache.xml.serialize.OutputFormat;
72 import org.apache.xml.serialize.XMLSerializer;
73 import org.jdom.Document;
74 import org.jdom.Element;
75 import org.jdom.output.XMLOutputter;
76 import org.slf4j.Logger;
77 import org.slf4j.LoggerFactory;
78 import org.xml.sax.InputSource;
79 import org.xml.sax.SAXException;
80 import org.xml.sax.XMLReader;
81 import org.xml.sax.helpers.XMLReaderFactory;
82
83 import EDU.oswego.cs.dl.util.concurrent.Sync;
84
85
86
87
88
89
90 public abstract class BaseSyndicatorImpl implements Syndicator {
91 private static final Logger log = LoggerFactory.getLogger(BaseSyndicatorImpl.class);
92
93
94
95
96 public static final String DEFAULT_HANDLER = ".magnolia/activation";
97
98 public static final String PARENT_PATH = "mgnlExchangeParentPath";
99
100 public static final String MAPPED_PARENT_PATH = "mgnlExchangeMappedParent";
101
102
103
104
105 public static final String PATH = "mgnlExchangePath";
106
107 public static final String NODE_UUID = "mgnlExchangeNodeUUID";
108
109 public static final String REPOSITORY_NAME = "mgnlExchangeRepositoryName";
110
111 public static final String WORKSPACE_NAME = "mgnlExchangeWorkspaceName";
112
113 public static final String VERSION_NAME = "mgnlExchangeVersionName";
114
115
116
117
118 public static final String RESOURCE_MAPPING_FILE = "mgnlExchangeResourceMappingFile";
119
120
121
122
123
124
125 public static final String SIBLINGS_ROOT_ELEMENT = "NodeSiblings";
126
127 public static final String SIBLINGS_ELEMENT = "sibling";
128
129 public static final String SIBLING_UUID = "siblingUUID";
130
131 public static final String RESOURCE_MAPPING_FILE_ELEMENT = "File";
132
133 public static final String RESOURCE_MAPPING_NAME_ATTRIBUTE = "name";
134
135 public static final String RESOURCE_MAPPING_UUID_ATTRIBUTE = "contentUUID";
136
137 public static final String RESOURCE_MAPPING_ID_ATTRIBUTE = "resourceId";
138
139 public static final String RESOURCE_MAPPING_ROOT_ELEMENT = "Resources";
140
141 public static final String ACTION = "mgnlExchangeAction";
142
143 public static final String ACTIVATE = "activate";
144
145 public static final String DEACTIVATE = "deactivate";
146
147 public static final String COMMIT = "commit";
148
149 public static final String ROLLBACK = "rollback";
150
151 public static final String AUTHORIZATION = "Authorization";
152
153 public static final String AUTH_CREDENTIALS= "mgnlUserPSWD";
154
155 public static final String AUTH_USER = "mgnlUserId";
156
157 public static final String CONTENT_FILTER_RULE = "mgnlExchangeFilterRule";
158
159 public static final String ACTIVATION_SUCCESSFUL = "sa_success";
160
161 public static final String ACTIVATION_FAILED = "sa_failed";
162
163 public static final String ACTIVATION_ATTRIBUTE_STATUS = "sa_attribute_status";
164
165 public static final String ACTIVATION_ATTRIBUTE_MESSAGE = "sa_attribute_message";
166
167 public static final String ACTIVATION_ATTRIBUTE_VERSION = "sa_attribute_version";
168
169
170
171
172
173
174
175 protected static void executeInPool(Runnable job) throws ExchangeException {
176 try {
177 ThreadPool.getInstance().execute(job);
178 } catch (InterruptedException e) {
179
180
181
182
183 String message = "could not execute job in pool";
184 log.error(message, e);
185 throw new ExchangeException(message, e);
186 }
187 }
188
189
190
191
192
193
194
195
196
197
198 protected static void acquireIgnoringInterruption(Sync latch) {
199 try {
200 latch.acquire();
201 } catch (InterruptedException e) {
202
203 acquireIgnoringInterruption(latch);
204
205 Thread.currentThread().interrupt();
206 }
207 }
208
209 protected String repositoryName;
210
211 protected String workspaceName;
212
213 protected String parent;
214
215 protected String path;
216
217 protected String nodeUUID;
218
219 protected Content.ContentFilter contentFilter;
220
221 protected Rule contentFilterRule;
222
223 protected User user;
224
225 protected String basicCredentials;
226
227
228
229
230
231
232
233
234
235 public void init(User user, String repositoryName, String workspaceName, Rule rule) {
236 this.user = user;
237 this.basicCredentials = "Basic "
238 + new String(Base64.encodeBase64((this.user.getName() + ":" + this.user.getPassword()).getBytes()));
239 this.contentFilter = new RuleBasedContentFilter(rule);
240 this.contentFilterRule = rule;
241 this.repositoryName = repositoryName;
242 this.workspaceName = workspaceName;
243 }
244
245
246
247
248
249
250
251
252
253 public void activate(String parent, Content content) throws ExchangeException, RepositoryException {
254 this.activate(parent, content, null);
255 }
256
257
258
259
260
261
262
263
264
265
266
267 public void activate(String parent, Content content, List orderBefore) throws ExchangeException, RepositoryException {
268 this.activate(null, parent, content, orderBefore);
269 }
270
271
272
273
274
275
276
277
278
279
280 public void activate(Subscriber subscriber, String parent, Content content) throws ExchangeException, RepositoryException {
281 this.activate(subscriber, parent, content, null);
282 }
283
284
285
286
287
288
289
290
291
292
293
294 public void activate(Subscriber subscriber, String parent, Content content, List orderBefore) throws ExchangeException, RepositoryException {
295 this.parent = parent;
296 this.path = content.getHandle();
297 ActivationContent activationContent = null;
298 try {
299 activationContent = this.collect(content, orderBefore);
300 if (null == subscriber) {
301 this.activate(activationContent);
302 } else {
303 this.activate(subscriber, activationContent);
304 }
305 this.updateActivationDetails();
306 log.info("Exchange: activation succeeded [{}]", content.getHandle());
307 }
308 catch (Exception e) {
309 if (log.isDebugEnabled()) {
310 log.error("Exchange: activation failed for path:" + ((path != null) ? path : "[null]"), e);
311 long timestamp = System.currentTimeMillis();
312 log.warn("moving files from failed activation to *.failed" + timestamp );
313 Iterator keys = activationContent.getFiles().values().iterator();
314 while (keys.hasNext()) {
315 File f = (File) keys.next();
316 f.renameTo(new File(f.getAbsolutePath()+".failed" + timestamp));
317 }
318 activationContent.getFiles().clear();
319
320 }
321 throw new ExchangeException(e);
322 }
323 finally {
324 log.debug("Cleaning temporary files");
325 cleanTemporaryStore(activationContent);
326 }
327 }
328
329
330
331
332 public abstract void activate(ActivationContent activationContent) throws ExchangeException;
333
334
335
336
337
338 public abstract String activate(Subscriber subscriber, ActivationContent activationContent) throws ExchangeException;
339
340
341
342
343 protected void cleanTemporaryStore(ActivationContent activationContent) {
344 if (activationContent == null) {
345 log.debug("Clean temporary store - nothing to do");
346 return;
347 }
348 if (log.isDebugEnabled()) {
349 log.debug("Debugging is enabled. Keeping temporary files in store for debugging purposes. Clean the store manually once done with debugging.");
350 return;
351 }
352
353 Iterator keys = activationContent.getFiles().keySet().iterator();
354 while (keys.hasNext()) {
355 String key = (String) keys.next();
356 log.debug("Removing temporary file {}", key);
357 activationContent.getFile(key).delete();
358 }
359 }
360
361 public synchronized void deactivate(String path) throws ExchangeException, RepositoryException {
362 final Content node = getHierarchyManager().getContent(path);
363 deactivate(node);
364 }
365
366
367
368
369
370
371 public synchronized void deactivate(Content node) throws ExchangeException, RepositoryException {
372 this.nodeUUID = node.getUUID();
373 this.path = node.getHandle();
374 this.doDeactivate();
375 updateDeactivationDetails();
376 }
377
378
379
380
381
382
383
384 public synchronized void deactivate(Subscriber subscriber, Content node) throws ExchangeException, RepositoryException {
385 this.nodeUUID = node.getUUID();
386 this.path = node.getHandle();
387 this.doDeactivate(subscriber);
388 updateDeactivationDetails();
389 }
390
391
392
393
394 public abstract void doDeactivate() throws ExchangeException;
395
396
397
398
399
400
401 public abstract String doDeactivate(Subscriber subscriber) throws ExchangeException;
402
403
404
405
406
407 protected String getDeactivationURL(Subscriber subscriberInfo) {
408 return getActivationURL(subscriberInfo);
409 }
410
411
412
413
414
415 protected void addDeactivationHeaders(URLConnection connection) {
416 connection.addRequestProperty(REPOSITORY_NAME, this.repositoryName);
417 connection.addRequestProperty(WORKSPACE_NAME, this.workspaceName);
418 connection.addRequestProperty(NODE_UUID, this.nodeUUID);
419 connection.addRequestProperty(ACTION, DEACTIVATE);
420 }
421
422
423
424
425 protected String getActivationURL(Subscriber subscriberInfo) {
426 final String url = subscriberInfo.getURL();
427 if (!url.endsWith("/")) {
428 return url + "/" + DEFAULT_HANDLER;
429 }
430 return url + DEFAULT_HANDLER;
431 }
432
433
434
435
436 protected void addActivationHeaders(URLConnection connection, ActivationContent activationContent) {
437 Iterator headerKeys = activationContent.getProperties().keySet().iterator();
438 while (headerKeys.hasNext()) {
439 String key = (String) headerKeys.next();
440 String value = activationContent.getproperty(key);
441 if(SystemProperty.getBooleanProperty(SystemProperty.MAGNOLIA_UTF8_ENABLED)) {
442 try {
443 value = URLEncoder.encode(value, "UTF-8");
444 }
445 catch (UnsupportedEncodingException e) {
446
447 }
448 }
449 connection.setRequestProperty(key, value);
450 }
451 }
452
453
454
455
456 protected void updateActivationDetails() throws RepositoryException {
457
458 Content page = getSystemHierarchyManager().getContent(this.path);
459 updateMetaData(page, ACTIVATE);
460 page.save();
461 AuditLoggingUtil.log(AuditLoggingUtil.ACTION_ACTIVATE, this.workspaceName, page.getItemType(), this.path );
462 }
463
464
465
466
467 protected void updateDeactivationDetails() throws RepositoryException {
468
469 Content page = getSystemHierarchyManager().getContentByUUID(this.nodeUUID);
470 updateMetaData(page, DEACTIVATE);
471 page.save();
472 AuditLoggingUtil.log(AuditLoggingUtil.ACTION_DEACTIVATE, this.workspaceName, page.getItemType(), page.getHandle() );
473 }
474
475
476 private HierarchyManager getHierarchyManager() {
477 return MgnlContext.getHierarchyManager(this.repositoryName, this.workspaceName);
478 }
479
480 private HierarchyManager getSystemHierarchyManager() {
481 return MgnlContext.getSystemContext().getHierarchyManager(this.repositoryName, this.workspaceName);
482 }
483
484
485
486
487
488 protected void updateMetaData(Content node, String type) throws AccessDeniedException {
489
490 MetaData md = node.getMetaData();
491 if (type.equals(ACTIVATE)) {
492 md.setActivated();
493 }
494 else {
495 md.setUnActivated();
496 }
497 md.setActivatorId(this.user.getName());
498 md.setLastActivationActionDate();
499
500 Iterator children;
501 if (type.equals(ACTIVATE)) {
502
503 children = node.getChildren(this.contentFilter).iterator();
504 }
505 else {
506
507 children = node.getChildren(ContentUtil.EXCLUDE_META_DATA_CONTENT_FILTER).iterator();
508 }
509
510 while (children.hasNext()) {
511 Content child = (Content) children.next();
512 this.updateMetaData(child, type);
513 }
514
515
516 }
517
518
519
520
521
522 protected ActivationContent collect(Content node, List orderBefore) throws Exception {
523 File resourceFile = File.createTempFile("resources", ".xml", Path.getTempDirectory());
524
525 ActivationContent activationContent = new ActivationContent();
526
527 activationContent.addProperty(PARENT_PATH, this.parent);
528 activationContent.addProperty(WORKSPACE_NAME, this.workspaceName);
529 activationContent.addProperty(REPOSITORY_NAME, this.repositoryName);
530 activationContent.addProperty(RESOURCE_MAPPING_FILE, resourceFile.getName());
531 activationContent.addProperty(ACTION, ACTIVATE);
532 activationContent.addProperty(CONTENT_FILTER_RULE, this.contentFilterRule.toString());
533 activationContent.addProperty(NODE_UUID, node.getUUID());
534
535
536 Document document = new Document();
537 Element root = new Element(RESOURCE_MAPPING_ROOT_ELEMENT);
538 document.setRootElement(root);
539
540 addOrderingInfo(root, orderBefore);
541
542 this.addResources(root, node.getWorkspace().getSession(), node, this.contentFilter, activationContent);
543 XMLOutputter outputter = new XMLOutputter();
544 outputter.output(document, new FileOutputStream(resourceFile));
545
546
547 activationContent.addFile(resourceFile.getName(), resourceFile);
548
549 return activationContent;
550 }
551
552
553
554
555
556
557 protected void addOrderingInfo(Element root, List orderBefore) {
558
559 Element siblingRoot = new Element(SIBLINGS_ROOT_ELEMENT);
560 root.addContent(siblingRoot);
561 if (orderBefore == null) return;
562 Iterator siblings = orderBefore.iterator();
563 while (siblings.hasNext()) {
564 String uuid = (String) siblings.next();
565 Element e = new Element(SIBLINGS_ELEMENT);
566 e.setAttribute(SIBLING_UUID, uuid);
567 siblingRoot.addContent(e);
568 }
569 }
570
571 protected void addResources(Element resourceElement, Session session, Content content, Content.ContentFilter filter, ActivationContent activationContent) throws IOException, RepositoryException, SAXException, Exception {
572 File file = File.createTempFile("exchange_" + content.getUUID(), ".xml.gz", Path.getTempDirectory());
573 GZIPOutputStream gzipOutputStream = new GZIPOutputStream(new FileOutputStream(file));
574
575
576 if (content.isNodeType("nt:frozenNode") || content.getWorkspace().getName().equals(ContentRepository.VERSION_STORE)) {
577 XMLReader elementfilter = new FrozenElementFilter(XMLReaderFactory
578 .createXMLReader(org.apache.xerces.parsers.SAXParser.class.getName()));
579 ((FrozenElementFilter) elementfilter).setNodeName(content.getName());
580
581
582
583 boolean noRecurse = !content.isNodeType(ItemType.NT_FILE);
584 exportAndParse(session, content, elementfilter, gzipOutputStream, noRecurse);
585 } else {
586
587
588
589 if (content.isNodeType(ItemType.NT_FILE)) {
590 session.exportSystemView(content.getJCRNode().getPath(), gzipOutputStream, false, false);
591 } else {
592 session.exportSystemView(content.getJCRNode().getPath(), gzipOutputStream, false, true);
593 }
594 }
595
596 IOUtils.closeQuietly(gzipOutputStream);
597
598 Element element = new Element(RESOURCE_MAPPING_FILE_ELEMENT);
599 element.setAttribute(RESOURCE_MAPPING_NAME_ATTRIBUTE, content.getName());
600 element.setAttribute(RESOURCE_MAPPING_UUID_ATTRIBUTE, content.getUUID());
601 element.setAttribute(RESOURCE_MAPPING_ID_ATTRIBUTE, file.getName());
602 resourceElement.addContent(element);
603
604 activationContent.addFile(file.getName(), file);
605
606 Iterator children = content.getChildren(filter).iterator();
607 while (children.hasNext()) {
608 Content child = (Content) children.next();
609 this.addResources(element, session, child, filter, activationContent);
610 }
611 }
612
613 protected void exportAndParse(Session session, Content content, XMLReader elementfilter, OutputStream os, boolean noRecurse) throws Exception {
614 File tempFile = File.createTempFile("Frozen_"+content.getName(), ".xml");
615 OutputStream tmpFileOutStream = null;
616 FileInputStream tmpFileInStream = null;
617 try {
618 tmpFileOutStream = new FileOutputStream(tempFile);
619
620 session.exportSystemView(content.getJCRNode().getPath(), tmpFileOutStream, false, noRecurse);
621 tmpFileOutStream.flush();
622 tmpFileOutStream.close();
623
624 OutputFormat outputFormat = new OutputFormat();
625 outputFormat.setPreserveSpace(false);
626
627 tmpFileInStream = new FileInputStream(tempFile);
628 elementfilter.setContentHandler(new XMLSerializer(os, outputFormat));
629 elementfilter.parse(new InputSource(tmpFileInStream));
630 tmpFileInStream.close();
631 } catch (Throwable t) {
632 log.error("Failed to parse XML using FrozenElementFilter",t);
633 throw new Exception(t);
634 } finally {
635 IOUtils.closeQuietly(tmpFileInStream);
636 IOUtils.closeQuietly(tmpFileOutStream);
637 tempFile.delete();
638 }
639 }
640
641
642 }