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.importexport;
35
36 import info.magnolia.cms.beans.runtime.Document;
37 import info.magnolia.cms.core.SystemProperty;
38 import info.magnolia.cms.core.version.VersionManager;
39 import info.magnolia.context.MgnlContext;
40 import info.magnolia.importexport.filters.AccesscontrolNodeFilter;
41 import info.magnolia.importexport.filters.ImportXmlRootFilter;
42 import info.magnolia.importexport.filters.MagnoliaV2Filter;
43 import info.magnolia.importexport.filters.MetadataUuidFilter;
44 import info.magnolia.importexport.filters.RemoveMixversionableFilter;
45 import info.magnolia.importexport.filters.VersionFilter;
46 import info.magnolia.importexport.postprocessors.ActivationStatusImportPostProcessor;
47 import info.magnolia.importexport.postprocessors.MetaDataImportPostProcessor;
48 import info.magnolia.importexport.postprocessors.UpdateVersionMixinPostProcessor;
49 import info.magnolia.jcr.util.NodeTypes;
50 import info.magnolia.jcr.util.NodeUtil;
51 import info.magnolia.jcr.util.SessionUtil;
52 import info.magnolia.objectfactory.Components;
53
54 import java.io.File;
55 import java.io.FileInputStream;
56 import java.io.FileNotFoundException;
57 import java.io.FileOutputStream;
58 import java.io.IOException;
59 import java.io.InputStream;
60 import java.io.OutputStream;
61 import java.io.UnsupportedEncodingException;
62 import java.net.URLDecoder;
63 import java.net.URLEncoder;
64 import java.util.Iterator;
65 import java.util.List;
66 import java.util.Properties;
67 import java.util.regex.Matcher;
68 import java.util.regex.Pattern;
69 import java.util.zip.DeflaterOutputStream;
70 import java.util.zip.GZIPInputStream;
71 import java.util.zip.GZIPOutputStream;
72 import java.util.zip.ZipInputStream;
73 import java.util.zip.ZipOutputStream;
74
75 import javax.jcr.ImportUUIDBehavior;
76 import javax.jcr.Node;
77 import javax.jcr.NodeIterator;
78 import javax.jcr.PathNotFoundException;
79 import javax.jcr.RepositoryException;
80 import javax.jcr.Session;
81 import javax.xml.transform.Source;
82 import javax.xml.transform.sax.SAXTransformerFactory;
83 import javax.xml.transform.stream.StreamSource;
84
85 import org.apache.commons.io.IOUtils;
86 import org.apache.commons.lang3.StringUtils;
87 import org.apache.xml.serialize.OutputFormat;
88 import org.apache.xml.serialize.XMLSerializer;
89 import org.slf4j.Logger;
90 import org.slf4j.LoggerFactory;
91 import org.xml.sax.ContentHandler;
92 import org.xml.sax.InputSource;
93 import org.xml.sax.SAXException;
94 import org.xml.sax.XMLFilter;
95 import org.xml.sax.XMLReader;
96 import org.xml.sax.helpers.XMLReaderFactory;
97
98
99
100
101 public class DataTransporter {
102
103 private static final Pattern DOT_NAME_PATTERN = Pattern.compile("[\\w\\-]*\\.*[\\w\\-]*");
104
105 private static final int INDENT_VALUE = 2;
106
107 private static Logger log = LoggerFactory.getLogger(DataTransporter.class.getName());
108
109 final static int BOOTSTRAP_IMPORT_MODE = ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING;
110
111 public static final String ZIP = ".zip";
112
113 public static final String GZ = ".gz";
114
115 public static final String XML = ".xml";
116
117 public static final String PROPERTIES = ".properties";
118
119 public static final String DOT = ".";
120
121 public static final String SLASH = "/";
122
123 public static final String UTF8 = "UTF-8";
124
125 public static final String JCR_ROOT = "jcr:root";
126
127
128
129
130
131
132
133
134
135
136
137 public static synchronized void importDocument(Document xmlDocument, String repositoryName, String basepath,
138 boolean keepVersionHistory, int importMode, boolean saveAfterImport,
139 boolean createBasepathIfNotExist)
140 throws IOException {
141 File xmlFile = xmlDocument.getFile();
142 importFile(xmlFile, repositoryName, basepath, keepVersionHistory, importMode, saveAfterImport,
143 createBasepathIfNotExist);
144 }
145
146
147
148
149
150
151
152
153
154
155
156 public static synchronized void importFile(File xmlFile, String repositoryName, String basepath,
157 boolean keepVersionHistory, int importMode, boolean saveAfterImport,
158 boolean createBasepathIfNotExist)
159 throws IOException {
160 String name = xmlFile.getAbsolutePath();
161
162 InputStream xmlStream = getInputStreamForFile(xmlFile);
163 importXmlStream(xmlStream, repositoryName, basepath, name, keepVersionHistory, importMode, saveAfterImport,
164 createBasepathIfNotExist);
165 }
166
167 public static void executeBootstrapImport(File xmlFile, String repositoryName) throws IOException {
168 String filenameWithoutExt = StringUtils.substringBeforeLast(xmlFile.getName(), DOT);
169 if (filenameWithoutExt.endsWith(XML)) {
170
171
172 filenameWithoutExt = StringUtils.substringBeforeLast(xmlFile.getName(), DOT);
173 }
174 String pathName = StringUtils.substringAfter(StringUtils.substringBeforeLast(filenameWithoutExt, DOT), DOT);
175
176 pathName = decodePath(pathName, UTF8);
177
178 String basepath = SLASH + StringUtils.replace(pathName, DOT, SLASH);
179
180 if (xmlFile.getName().endsWith(PROPERTIES)) {
181 Properties properties = new Properties();
182 FileInputStream stream = new FileInputStream(xmlFile);
183 try {
184 properties.load(stream);
185 } finally {
186 IOUtils.closeQuietly(stream);
187 }
188 importProperties(properties, repositoryName);
189 } else {
190 DataTransporter.importFile(xmlFile, repositoryName, basepath, false, BOOTSTRAP_IMPORT_MODE, true, true);
191 }
192 }
193
194
195
196
197 @Deprecated
198 public static void importProperties(Properties properties, String repositoryName) {
199 for (Iterator iter = properties.keySet().iterator(); iter.hasNext(); ) {
200 String key = (String) iter.next();
201 String value = (String) properties.get(key);
202
203 String name = StringUtils.substringAfterLast(key, ".");
204 String path = StringUtils.substringBeforeLast(key, ".").replace('.', '/');
205 Node node = SessionUtil.getNode(repositoryName, path);
206 if (node != null) {
207 try {
208 node.setProperty(name, value);
209 node.getSession().save();
210 } catch (RepositoryException e) {
211 log.error("can't set property {}", key, e);
212 }
213 }
214 }
215
216 }
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234 public static synchronized void importXmlStream(InputStream xmlStream, String repositoryName, String basepath,
235 String name, boolean keepVersionHistory, int importMode,
236 boolean saveAfterImport, boolean createBasepathIfNotExist)
237 throws IOException {
238 importXmlStream(xmlStream, repositoryName, basepath, name, keepVersionHistory, false, importMode, saveAfterImport, createBasepathIfNotExist);
239
240 }
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258 public static synchronized void importXmlStream(InputStream xmlStream, String repositoryName, String basepath,
259 String name, boolean keepVersionHistory, boolean forceUnpublishState, int importMode,
260 boolean saveAfterImport, boolean createBasepathIfNotExist)
261 throws IOException {
262
263
264 if (xmlStream == null) {
265 throw new IOException("Can't import a null stream into repository: " + repositoryName + ", basepath: " + basepath + ", name: " + name);
266 }
267 try {
268 Session session = MgnlContext.getJCRSession(repositoryName);
269
270 log.debug("Importing content into repository: [{}] from: [{}] into path: [{}]", repositoryName, name, basepath);
271
272 if (!session.nodeExists(basepath) && createBasepathIfNotExist) {
273 try {
274 NodeUtil.createPath(session.getRootNode(), basepath, NodeTypes.Content.NAME);
275 } catch (RepositoryException e) {
276 log.error("can't create path [{}]", basepath);
277 }
278 }
279
280
281 List<Node> nodesBeforeImport = NodeUtil.asList(NodeUtil.asIterable(session.getNode(basepath).getNodes()));
282
283 if (keepVersionHistory) {
284
285 session.importXML(basepath, xmlStream, importMode);
286 } else {
287
288 XMLReader initialReader = XMLReaderFactory.createXMLReader(org.apache.xerces.parsers.SAXParser.class.getName());
289 try {
290 initialReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
291 } catch (SAXException e) {
292 log.error("could not set parser feature");
293 }
294
295
296 XMLFilter magnoliaV2Filter = null;
297
298
299 if (new File(name).isFile()) {
300 InputStream xslStream = getXslStreamForXmlFile(new File(name));
301 if (xslStream != null) {
302 Source xslSource = new StreamSource(xslStream);
303 SAXTransformerFactory saxTransformerFactory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
304 XMLFilter xslFilter = saxTransformerFactory.newXMLFilter(xslSource);
305 magnoliaV2Filter = new MagnoliaV2Filter(xslFilter);
306 }
307 }
308
309 if (magnoliaV2Filter == null) {
310 magnoliaV2Filter = new MagnoliaV2Filter(initialReader);
311 }
312
313 XMLFilter versionFilter = new VersionFilter(magnoliaV2Filter);
314
315
316
317
318
319 versionFilter = new RemoveMixversionableFilter(versionFilter);
320
321
322 versionFilter = new AccesscontrolNodeFilter(versionFilter);
323
324 XMLReader finalReader = new ImportXmlRootFilter(versionFilter);
325
326 ContentHandler handler = session.getImportContentHandler(basepath, importMode);
327 finalReader.setContentHandler(handler);
328
329
330 try {
331 finalReader.parse(new InputSource(xmlStream));
332 } finally {
333 IOUtils.closeQuietly(xmlStream);
334 }
335
336 if (((ImportXmlRootFilter) finalReader).rootNodeFound) {
337 String path = basepath;
338 if (!path.endsWith(SLASH)) {
339 path += SLASH;
340 }
341
342 Node dummyRoot = (Node) session.getItem(path + JCR_ROOT);
343 for (Iterator iter = dummyRoot.getNodes(); iter.hasNext(); ) {
344 Node child = (Node) iter.next();
345
346
347 if (session.itemExists(path + child.getName())) {
348 session.getItem(path + child.getName()).remove();
349 }
350
351 session.move(child.getPath(), path + child.getName());
352 }
353
354 dummyRoot.remove();
355 }
356
357
358 VersionManager versionManager = Components.getComponent(VersionManager.class);
359 NodeIterator nodesAfterImport = session.getNode(basepath).getNodes();
360 while (nodesAfterImport.hasNext()) {
361 Node nodeAfterImport = nodesAfterImport.nextNode();
362 boolean existedBeforeImport = false;
363 for (Node nodeBeforeImport : nodesBeforeImport) {
364 if (NodeUtil.isSame(nodeAfterImport, nodeBeforeImport)) {
365 existedBeforeImport = true;
366 break;
367 }
368 }
369 if (!existedBeforeImport) {
370 postProcessAfterImport(nodeAfterImport, forceUnpublishState, importMode, versionManager);
371 }
372 }
373 }
374 if (saveAfterImport) {
375 session.save();
376 }
377 } catch (Exception e) {
378 throw new RuntimeException("Error importing " + name + ": " + e.getMessage(), e);
379 } finally {
380 IOUtils.closeQuietly(xmlStream);
381 }
382 }
383
384 private static void postProcessAfterImport(Node node, boolean forceUnpublishState, int importMode, VersionManager versionManager) throws RepositoryException {
385 try {
386 new MetaDataImportPostProcessor().postProcessNode(node);
387 if (forceUnpublishState) {
388 new ActivationStatusImportPostProcessor().postProcessNode(node);
389 }
390 new UpdateVersionMixinPostProcessor(importMode, versionManager).postProcessNode(node);
391 } catch (RepositoryException e) {
392 throw new RepositoryException("Failed to post process imported nodes at path " + NodeUtil.getNodePathIfPossible(node) + ": " + e.getMessage(), e);
393 }
394 }
395
396
397
398
399 protected static InputStream getXslStreamForXmlFile(File file) {
400 InputStream xslStream = null;
401 String xlsFilename = StringUtils.substringBeforeLast(file.getAbsolutePath(), ".") + ".xsl";
402 File xslFile = new File(xlsFilename);
403 if (xslFile.exists()) {
404 try {
405 xslStream = new FileInputStream(xslFile);
406 log.info("XSL file for [{}] found ({})", file.getName(), xslFile.getName());
407 } catch (FileNotFoundException e) {
408 e.printStackTrace();
409 }
410 }
411 return xslStream;
412 }
413
414
415
416
417
418
419 private static InputStream getInputStreamForFile(File xmlFile) throws IOException {
420 InputStream xmlStream;
421
422 if (xmlFile.getName().endsWith(ZIP)) {
423 xmlStream = new ZipInputStream((new FileInputStream(xmlFile)));
424 } else if (xmlFile.getName().endsWith(GZ)) {
425 xmlStream = new GZIPInputStream((new FileInputStream(xmlFile)));
426 } else {
427 xmlStream = new FileInputStream(xmlFile);
428 }
429 return xmlStream;
430 }
431
432 public static void executeExport(OutputStream baseOutputStream, boolean keepVersionHistory, boolean format,
433 Session session, String basepath, String repository, String ext) throws IOException {
434 OutputStream outputStream = baseOutputStream;
435 if (ext.endsWith(ZIP)) {
436 outputStream = new ZipOutputStream(baseOutputStream);
437 } else if (ext.endsWith(GZ)) {
438 outputStream = new GZIPOutputStream(baseOutputStream);
439 }
440
441 try {
442 if (keepVersionHistory) {
443
444
445 if (!format) {
446 session.exportSystemView(basepath, outputStream, false, false);
447 } else {
448 parseAndFormat(outputStream, null, repository, basepath, session, false);
449 }
450 } else {
451
452
453 XMLReader reader = new VersionFilter(XMLReaderFactory
454 .createXMLReader(org.apache.xerces.parsers.SAXParser.class.getName()));
455 reader = new AccesscontrolNodeFilter(reader);
456 parseAndFormat(outputStream, reader, repository, basepath, session, false);
457 }
458 } catch (IOException e) {
459 throw new RuntimeException(e);
460 } catch (SAXException e) {
461 throw new RuntimeException(e);
462 } catch (RepositoryException e) {
463 throw new RuntimeException(e);
464 }
465
466
467
468 if (outputStream instanceof DeflaterOutputStream) {
469 ((DeflaterOutputStream) outputStream).finish();
470 }
471
472 baseOutputStream.flush();
473 IOUtils.closeQuietly(baseOutputStream);
474 }
475
476
477
478
479
480
481
482
483
484
485
486 public static void parseAndFormat(OutputStream stream, XMLReader reader, String repository, String basepath,
487 Session session, boolean noRecurse)
488 throws IOException, SAXException, PathNotFoundException, RepositoryException {
489
490 if (reader == null) {
491 reader = XMLReaderFactory.createXMLReader(org.apache.xerces.parsers.SAXParser.class.getName());
492 }
493
494
495 File tempFile = File.createTempFile("export-" + repository + session.getUserID(), ".xml");
496 OutputStream fileStream = new FileOutputStream(tempFile);
497
498 try {
499 session.exportSystemView(basepath, fileStream, false, noRecurse);
500 } finally {
501 IOUtils.closeQuietly(fileStream);
502 }
503
504 readFormatted(reader, tempFile, stream);
505
506 if (!tempFile.delete()) {
507 log.warn("Could not delete temporary export file {}", tempFile.getAbsolutePath());
508 }
509 }
510
511 protected static void readFormatted(XMLReader reader, File inputFile, OutputStream outputStream)
512 throws FileNotFoundException, IOException, SAXException {
513 InputStream fileInputStream = new FileInputStream(inputFile);
514 readFormatted(reader, fileInputStream, outputStream);
515 IOUtils.closeQuietly(fileInputStream);
516 }
517
518 protected static void readFormatted(XMLReader reader, InputStream inputStream, OutputStream outputStream)
519 throws FileNotFoundException, IOException, SAXException {
520
521 OutputFormat outputFormat = new OutputFormat();
522
523 outputFormat.setPreserveSpace(false);
524 outputFormat.setIndenting(true);
525 outputFormat.setIndent(INDENT_VALUE);
526 outputFormat.setLineWidth(120);
527
528 final boolean removeUnwantedNamespaces = !SystemProperty.getBooleanProperty("magnolia.export.keep_extra_namespaces");
529 MetadataUuidFilter metadataUuidFilter = new MetadataUuidFilter(reader, removeUnwantedNamespaces);
530 metadataUuidFilter.setContentHandler(new XMLSerializer(outputStream, outputFormat));
531 metadataUuidFilter.parse(new InputSource(inputStream));
532
533 IOUtils.closeQuietly(inputStream);
534 }
535
536
537
538
539
540
541
542 public static String encodePath(String path, String separator, String enc) {
543 StringBuilder pathEncoded = new StringBuilder();
544 try {
545 if (!StringUtils.contains(path, separator)) {
546 return URLEncoder.encode(path, enc);
547 }
548 for (int i = 0; i < path.length(); i++) {
549 String ch = String.valueOf(path.charAt(i));
550 if (separator.equals(ch)) {
551 pathEncoded.append(ch);
552 } else {
553 pathEncoded.append(URLEncoder.encode(ch, enc));
554 }
555 }
556 } catch (UnsupportedEncodingException e) {
557 return path;
558 }
559 return pathEncoded.toString();
560 }
561
562
563
564
565
566
567
568
569 public static String decodePath(String path, String enc) {
570 String pathEncoded = StringUtils.EMPTY;
571 try {
572 pathEncoded = URLDecoder.decode(path, enc);
573 } catch (UnsupportedEncodingException e) {
574 return path;
575 }
576 return pathEncoded;
577 }
578
579
580
581
582
583
584
585 public static String createExportPath(String path) {
586
587 String newPath = path.replace(".", "..");
588 newPath = newPath.replace("/", ".");
589 return newPath;
590 }
591
592
593
594
595
596 public static String revertExportPath(String exportPath) {
597 if (".".equals(exportPath)) {
598 return "/";
599 }
600
601
602 Matcher matcher = DOT_NAME_PATTERN.matcher(exportPath);
603
604 StringBuilder reversed = new StringBuilder(exportPath.length());
605
606 while (matcher.find()) {
607 String group = matcher.group();
608 int dotsNumber = StringUtils.countMatches(group, ".");
609 if (dotsNumber == 1) {
610 reversed.append(group.replaceFirst("\\.", "/"));
611 } else {
612 String dots = StringUtils.substringBeforeLast(group, ".").replace("..", ".");
613 String name = StringUtils.substringAfterLast(group, ".");
614 reversed.append(dots);
615
616 if (dotsNumber % 2 != 0) {
617 reversed.append("/");
618 }
619 reversed.append(name);
620 }
621 }
622 return reversed.toString();
623 }
624
625 }