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