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.CmsLog;
033import org.opencms.main.OpenCms;
034import org.opencms.util.CmsFileUtil;
035import org.opencms.util.CmsXmlSaxWriter;
036
037import java.io.File;
038import java.io.FileOutputStream;
039import java.io.FileWriter;
040import java.io.IOException;
041import java.io.StringWriter;
042import java.io.Writer;
043import java.util.zip.ZipEntry;
044import java.util.zip.ZipOutputStream;
045
046import org.apache.commons.logging.Log;
047
048import org.dom4j.io.SAXWriter;
049import org.xml.sax.SAXException;
050
051/**
052 * Wrapper to write exported OpenCms resources either to a .ZIP file or to the file system.<p>
053 *
054 * @since 7.5.1
055 */
056public class CmsExportHelper {
057
058    /** Length that can be safely written to ZIP output. */
059    private static final int SUB_LENGTH = 4096;
060
061    private static final Log LOG = CmsLog.getLog(CmsExportHelper.class);
062
063    /** The main export path. */
064    private String m_exportPath;
065
066    /** The export ZIP stream to write resources to. */
067    private ZipOutputStream m_exportZipStream;
068
069    /** Indicates if the resources are exported in one export .ZIP file or as individual files. */
070    private boolean m_isExportAsFiles;
071
072    /** The SAX writer for the Manifest file. */
073    private SAXWriter m_saxWriter;
074
075    /**
076     * Creates a new export helper.<p>
077     *
078     * @param exportPath the export path
079     * @param exportAsFiles indicates if the resources should be exported as individual files or in one big ZIP file
080     * @param validateXml indicates of the manifest.xml should be validated
081     *
082     * @throws SAXException in case of issues creating the manifest.xml
083     * @throws IOException in case of file access issues
084     */
085    public CmsExportHelper(String exportPath, boolean exportAsFiles, boolean validateXml)
086    throws SAXException, IOException {
087
088        m_exportPath = exportPath;
089        m_isExportAsFiles = exportAsFiles;
090
091        removeOldExport(exportPath);
092
093        Writer writer;
094        if (m_isExportAsFiles) {
095            m_exportPath = m_exportPath + "/";
096            // write to file system directly
097            String fileName = getRfsFileName(CmsImportExportManager.EXPORT_MANIFEST);
098            File rfsFile = new File(fileName);
099            rfsFile.getParentFile().mkdirs();
100            rfsFile.createNewFile();
101            writer = new FileWriter(rfsFile);
102        } else {
103            // make sure parent folders exist
104            File rfsFile = new File(m_exportPath);
105            rfsFile.getParentFile().mkdirs();
106            // create the export ZIP stream
107            m_exportZipStream = new ZipOutputStream(new FileOutputStream(m_exportPath));
108            // delegate writing to a String writer
109            writer = new StringWriter(SUB_LENGTH);
110        }
111
112        // generate the SAX XML writer
113        CmsXmlSaxWriter saxHandler = new CmsXmlSaxWriter(writer, OpenCms.getSystemInfo().getDefaultEncoding());
114        saxHandler.setEscapeXml(true);
115        saxHandler.setEscapeUnknownChars(true);
116        // start the document
117        saxHandler.startDocument();
118
119        // set the doctype if needed
120        if (validateXml) {
121            saxHandler.startDTD(
122                CmsImportExportManager.N_EXPORT,
123                null,
124                CmsConfigurationManager.DEFAULT_DTD_PREFIX + CmsImportVersion7.DTD_FILENAME);
125            saxHandler.endDTD();
126        }
127        // initialize the dom4j writer object
128        m_saxWriter = new SAXWriter(saxHandler, saxHandler);
129    }
130
131    /**
132     * Ensures the zip stream is closed (if there is one).
133     */
134    public void ensureZipStreamClosed() {
135
136        if (m_exportZipStream != null) {
137            try {
138                m_exportZipStream.close();
139            } catch (Exception e) {
140                LOG.info(e.getLocalizedMessage(), e);
141            }
142        }
143    }
144
145    /**
146     * Returns the SAX writer for the Manifest file.<p>
147     *
148     * @return the SAX writer for the Manifest file
149     */
150    public SAXWriter getSaxWriter() {
151
152        return m_saxWriter;
153    }
154
155    /**
156     * Writes a single OpenCms VFS file to the export.<p>
157     *
158     * @param file the OpenCms VFS file to write
159     * @param name the name of the file in the export
160     *
161     * @throws IOException in case of file access issues
162     */
163    public void writeFile(CmsFile file, String name) throws IOException {
164
165        if (m_isExportAsFiles) {
166            writeFile2Rfs(file, name);
167        } else {
168            writeFile2Zip(file, name);
169        }
170    }
171
172    /**
173     * Writes the OpenCms manifest.xml file to the export.<p>
174     *
175     * @param xmlSaxWriter the SAX writer to use
176     *
177     * @throws SAXException in case of issues creating the manifest.xml
178     * @throws IOException in case of file access issues
179     */
180    public void writeManifest(CmsXmlSaxWriter xmlSaxWriter) throws IOException, SAXException {
181
182        if (m_isExportAsFiles) {
183            writeManifest2Rfs(xmlSaxWriter);
184        } else {
185            writeManifest2Zip(xmlSaxWriter);
186        }
187    }
188
189    /**
190     * Returns the RFS file name for the given OpenCms VFS file name.<p>
191     *
192     * @param name the OpenCms VFS file name
193     *
194     * @return the RFS file name for the given OpenCms VFS file name
195     */
196    protected String getRfsFileName(String name) {
197
198        return m_exportPath + name;
199    }
200
201    /**
202     * Removes the old export output, which may be an existing file or directory.<p>
203     *
204     * @param exportPath the export output path
205     */
206    protected void removeOldExport(String exportPath) {
207
208        File output = new File(exportPath);
209        if (output.exists()) {
210            // the output already exists
211            if (output.isDirectory()) {
212                // purge the complete directory
213                CmsFileUtil.purgeDirectory(output);
214            } else {
215                // remove the existing file
216                if (m_isExportAsFiles) {
217                    // in case we write to a file we can just overwrite,
218                    // but for a folder we must remove an existing file
219                    output.delete();
220                }
221            }
222        }
223    }
224
225    /**
226     * Writes a single OpenCms VFS file to the RFS export.<p>
227     *
228     * @param file the OpenCms VFS file to write
229     * @param name the name of the file in the export
230     *
231     * @throws IOException in case of file access issues
232     */
233    protected void writeFile2Rfs(CmsFile file, String name) throws IOException {
234
235        String fileName = getRfsFileName(name);
236        File rfsFile = new File(fileName);
237        if (!rfsFile.getParentFile().exists()) {
238            rfsFile.getParentFile().mkdirs();
239        }
240        rfsFile.createNewFile();
241        FileOutputStream rfsFileOut = new FileOutputStream(rfsFile);
242        rfsFileOut.write(file.getContents());
243        rfsFileOut.close();
244    }
245
246    /**
247     * Writes a single OpenCms VFS file to the ZIP export.<p>
248     *
249     * @param file the OpenCms VFS file to write
250     * @param name the name of the file in the export
251     *
252     * @throws IOException in case of file access issues
253     */
254    protected void writeFile2Zip(CmsFile file, String name) throws IOException {
255
256        ZipEntry entry = new ZipEntry(name);
257        // save the time of the last modification in the zip
258        entry.setTime(file.getDateLastModified());
259        m_exportZipStream.putNextEntry(entry);
260        m_exportZipStream.write(file.getContents());
261        m_exportZipStream.closeEntry();
262    }
263
264    /**
265     * Writes the OpenCms manifest.xml file to the RFS export.<p>
266     *
267     * In case of the RFS export the file is directly written to a file output stream,
268     * so calling this method just closes the XML and finishes the stream.<p>
269     *
270     * @param xmlSaxWriter the SAX writer to use
271     *
272     * @throws SAXException in case of issues creating the manifest.xml
273     * @throws IOException in case of issues closing the file writer
274     */
275    protected void writeManifest2Rfs(CmsXmlSaxWriter xmlSaxWriter) throws SAXException, IOException {
276
277        // close the document - this will also trigger flushing the contents to the file system
278        xmlSaxWriter.endDocument();
279        xmlSaxWriter.getWriter().close();
280    }
281
282    /**
283     * Writes the OpenCms manifest.xml file to the ZIP export.<p>
284     *
285     * In case of the ZIP export the manifest is written to an internal StringBuffer
286     * first, which is then stored in the ZIP file when this method is called.<p>
287     *
288     * @param xmlSaxWriter the SAX writer to use
289     *
290     * @throws SAXException in case of issues creating the manifest.xml
291     * @throws IOException in case of file access issues
292     */
293    protected void writeManifest2Zip(CmsXmlSaxWriter xmlSaxWriter) throws IOException, SAXException {
294
295        // close the document
296        xmlSaxWriter.endDocument();
297        xmlSaxWriter.getWriter().close();
298
299        // create ZIP entry for the manifest XML document
300        ZipEntry entry = new ZipEntry(CmsImportExportManager.EXPORT_MANIFEST);
301        m_exportZipStream.putNextEntry(entry);
302
303        // complex substring operation is required to ensure handling for very large export manifest files
304        StringBuffer result = ((StringWriter)xmlSaxWriter.getWriter()).getBuffer();
305        int steps = result.length() / SUB_LENGTH;
306        int rest = result.length() % SUB_LENGTH;
307        int pos = 0;
308        for (int i = 0; i < steps; i++) {
309            String sub = result.substring(pos, pos + SUB_LENGTH);
310            m_exportZipStream.write(sub.getBytes(OpenCms.getSystemInfo().getDefaultEncoding()));
311            pos += SUB_LENGTH;
312        }
313        if (rest > 0) {
314            String sub = result.substring(pos, pos + rest);
315            m_exportZipStream.write(sub.getBytes(OpenCms.getSystemInfo().getDefaultEncoding()));
316        }
317
318        // close the zip entry for the manifest XML document
319        m_exportZipStream.closeEntry();
320
321        // finally close the zip stream
322        m_exportZipStream.close();
323    }
324}