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}