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