001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.importexport;
029
030import org.opencms.configuration.CmsConfigurationManager;
031import org.opencms.file.CmsFile;
032import org.opencms.main.OpenCms;
033import org.opencms.util.CmsFileUtil;
034import org.opencms.util.CmsXmlSaxWriter;
035
036import java.io.File;
037import java.io.FileOutputStream;
038import java.io.FileWriter;
039import java.io.IOException;
040import java.io.StringWriter;
041import java.io.Writer;
042import java.util.zip.ZipEntry;
043import java.util.zip.ZipOutputStream;
044
045import org.dom4j.io.SAXWriter;
046import org.xml.sax.SAXException;
047
048/**
049 * Wrapper to write exported OpenCms resources either to a .ZIP file or to the file system.<p>
050 *
051 * @since 7.5.1
052 */
053public class CmsExportHelper {
054
055    /** Length that can be safely written to ZIP output. */
056    private static final int SUB_LENGTH = 4096;
057
058    /** The main export path. */
059    private String m_exportPath;
060
061    /** The export ZIP stream to write resources to. */
062    private ZipOutputStream m_exportZipStream;
063
064    /** Indicates if the resources are exported in one export .ZIP file or as individual files. */
065    private boolean m_isExportAsFiles;
066
067    /** The SAX writer for the Manifest file. */
068    private SAXWriter m_saxWriter;
069
070    /**
071     * Creates a new export helper.<p>
072     *
073     * @param exportPath the export path
074     * @param exportAsFiles indicates if the resources should be exported as individual files or in one big ZIP file
075     * @param validateXml indicates of the manifest.xml should be validated
076     *
077     * @throws SAXException in case of issues creating the manifest.xml
078     * @throws IOException in case of file access issues
079     */
080    public CmsExportHelper(String exportPath, boolean exportAsFiles, boolean validateXml)
081    throws SAXException, IOException {
082
083        m_exportPath = exportPath;
084        m_isExportAsFiles = exportAsFiles;
085
086        removeOldExport(exportPath);
087
088        Writer writer;
089        if (m_isExportAsFiles) {
090            m_exportPath = m_exportPath + "/";
091            // write to file system directly
092            String fileName = getRfsFileName(CmsImportExportManager.EXPORT_MANIFEST);
093            File rfsFile = new File(fileName);
094            rfsFile.getParentFile().mkdirs();
095            rfsFile.createNewFile();
096            writer = new FileWriter(rfsFile);
097        } else {
098            // make sure parent folders exist
099            File rfsFile = new File(m_exportPath);
100            rfsFile.getParentFile().mkdirs();
101            // create the export ZIP stream
102            m_exportZipStream = new ZipOutputStream(new FileOutputStream(m_exportPath));
103            // delegate writing to a String writer
104            writer = new StringWriter(SUB_LENGTH);
105        }
106
107        // generate the SAX XML writer
108        CmsXmlSaxWriter saxHandler = new CmsXmlSaxWriter(writer, OpenCms.getSystemInfo().getDefaultEncoding());
109        saxHandler.setEscapeXml(true);
110        saxHandler.setEscapeUnknownChars(true);
111        // start the document
112        saxHandler.startDocument();
113
114        // set the doctype if needed
115        if (validateXml) {
116            saxHandler.startDTD(
117                CmsImportExportManager.N_EXPORT,
118                null,
119                CmsConfigurationManager.DEFAULT_DTD_PREFIX + CmsImportVersion7.DTD_FILENAME);
120            saxHandler.endDTD();
121        }
122        // initialize the dom4j writer object
123        m_saxWriter = new SAXWriter(saxHandler, saxHandler);
124    }
125
126    /**
127     * Returns the SAX writer for the Manifest file.<p>
128     *
129     * @return the SAX writer for the Manifest file
130     */
131    public SAXWriter getSaxWriter() {
132
133        return m_saxWriter;
134    }
135
136    /**
137     * Writes a single OpenCms VFS file to the export.<p>
138     *
139     * @param file the OpenCms VFS file to write
140     * @param name the name of the file in the export
141     *
142     * @throws IOException in case of file access issues
143     */
144    public void writeFile(CmsFile file, String name) throws IOException {
145
146        if (m_isExportAsFiles) {
147            writeFile2Rfs(file, name);
148        } else {
149            writeFile2Zip(file, name);
150        }
151    }
152
153    /**
154     * Writes the OpenCms manifest.xml file to the export.<p>
155     *
156     * @param xmlSaxWriter the SAX writer to use
157     *
158     * @throws SAXException in case of issues creating the manifest.xml
159     * @throws IOException in case of file access issues
160     */
161    public void writeManifest(CmsXmlSaxWriter xmlSaxWriter) throws IOException, SAXException {
162
163        if (m_isExportAsFiles) {
164            writeManifest2Rfs(xmlSaxWriter);
165        } else {
166            writeManifest2Zip(xmlSaxWriter);
167        }
168    }
169
170    /**
171     * Returns the RFS file name for the given OpenCms VFS file name.<p>
172     *
173     * @param name the OpenCms VFS file name
174     *
175     * @return the RFS file name for the given OpenCms VFS file name
176     */
177    protected String getRfsFileName(String name) {
178
179        return m_exportPath + name;
180    }
181
182    /**
183     * Removes the old export output, which may be an existing file or directory.<p>
184     *
185     * @param exportPath the export output path
186     */
187    protected void removeOldExport(String exportPath) {
188
189        File output = new File(exportPath);
190        if (output.exists()) {
191            // the output already exists
192            if (output.isDirectory()) {
193                // purge the complete directory
194                CmsFileUtil.purgeDirectory(output);
195            } else {
196                // remove the existing file
197                if (m_isExportAsFiles) {
198                    // in case we write to a file we can just overwrite,
199                    // but for a folder we must remove an existing file
200                    output.delete();
201                }
202            }
203        }
204    }
205
206    /**
207     * Writes a single OpenCms VFS file to the RFS export.<p>
208     *
209     * @param file the OpenCms VFS file to write
210     * @param name the name of the file in the export
211     *
212     * @throws IOException in case of file access issues
213     */
214    protected void writeFile2Rfs(CmsFile file, String name) throws IOException {
215
216        String fileName = getRfsFileName(name);
217        File rfsFile = new File(fileName);
218        if (!rfsFile.getParentFile().exists()) {
219            rfsFile.getParentFile().mkdirs();
220        }
221        rfsFile.createNewFile();
222        FileOutputStream rfsFileOut = new FileOutputStream(rfsFile);
223        rfsFileOut.write(file.getContents());
224        rfsFileOut.close();
225    }
226
227    /**
228     * Writes a single OpenCms VFS file to the ZIP export.<p>
229     *
230     * @param file the OpenCms VFS file to write
231     * @param name the name of the file in the export
232     *
233     * @throws IOException in case of file access issues
234     */
235    protected void writeFile2Zip(CmsFile file, String name) throws IOException {
236
237        ZipEntry entry = new ZipEntry(name);
238        // save the time of the last modification in the zip
239        entry.setTime(file.getDateLastModified());
240        m_exportZipStream.putNextEntry(entry);
241        m_exportZipStream.write(file.getContents());
242        m_exportZipStream.closeEntry();
243    }
244
245    /**
246     * Writes the OpenCms manifest.xml file to the RFS export.<p>
247     *
248     * In case of the RFS export the file is directly written to a file output stream,
249     * so calling this method just closes the XML and finishes the stream.<p>
250     *
251     * @param xmlSaxWriter the SAX writer to use
252     *
253     * @throws SAXException in case of issues creating the manifest.xml
254     * @throws IOException in case of issues closing the file writer
255     */
256    protected void writeManifest2Rfs(CmsXmlSaxWriter xmlSaxWriter) throws SAXException, IOException {
257
258        // close the document - this will also trigger flushing the contents to the file system
259        xmlSaxWriter.endDocument();
260        xmlSaxWriter.getWriter().close();
261    }
262
263    /**
264     * Writes the OpenCms manifest.xml file to the ZIP export.<p>
265     *
266     * In case of the ZIP export the manifest is written to an internal StringBuffer
267     * first, which is then stored in the ZIP file when this method is called.<p>
268     *
269     * @param xmlSaxWriter the SAX writer to use
270     *
271     * @throws SAXException in case of issues creating the manifest.xml
272     * @throws IOException in case of file access issues
273     */
274    protected void writeManifest2Zip(CmsXmlSaxWriter xmlSaxWriter) throws IOException, SAXException {
275
276        // close the document
277        xmlSaxWriter.endDocument();
278        xmlSaxWriter.getWriter().close();
279
280        // create ZIP entry for the manifest XML document
281        ZipEntry entry = new ZipEntry(CmsImportExportManager.EXPORT_MANIFEST);
282        m_exportZipStream.putNextEntry(entry);
283
284        // complex substring operation is required to ensure handling for very large export manifest files
285        StringBuffer result = ((StringWriter)xmlSaxWriter.getWriter()).getBuffer();
286        int steps = result.length() / SUB_LENGTH;
287        int rest = result.length() % SUB_LENGTH;
288        int pos = 0;
289        for (int i = 0; i < steps; i++) {
290            String sub = result.substring(pos, pos + SUB_LENGTH);
291            m_exportZipStream.write(sub.getBytes(OpenCms.getSystemInfo().getDefaultEncoding()));
292            pos += SUB_LENGTH;
293        }
294        if (rest > 0) {
295            String sub = result.substring(pos, pos + rest);
296            m_exportZipStream.write(sub.getBytes(OpenCms.getSystemInfo().getDefaultEncoding()));
297        }
298
299        // close the zip entry for the manifest XML document
300        m_exportZipStream.closeEntry();
301
302        // finally close the zip stream
303        m_exportZipStream.close();
304    }
305}