/* ****************************************************************************** * Copyright (c) 2006-2012 XMind Ltd. and others. * * This file is a part of XMind 3. XMind releases 3 and * above are dual-licensed under the Eclipse Public License (EPL), * which is available at http://www.eclipse.org/legal/epl-v10.html * and the GNU Lesser General Public License (LGPL), * which is available at http://www.gnu.org/licenses/lgpl.html * See http://www.xmind.net/license.html for details. * * Contributors: * XMind Ltd. - initial API and implementation *******************************************************************************/ package org.xmind.core.internal.dom; import static org.xmind.core.internal.dom.DOMConstants.ATTR_ID; import static org.xmind.core.internal.dom.DOMConstants.TAG_SHEET; import static org.xmind.core.internal.zip.ArchiveConstants.CONTENT_XML; import static org.xmind.core.internal.zip.ArchiveConstants.MANIFEST_XML; import static org.xmind.core.internal.zip.ArchiveConstants.META_XML; import static org.xmind.core.internal.zip.ArchiveConstants.PATH_MARKER_SHEET; import static org.xmind.core.internal.zip.ArchiveConstants.PATH_REVISIONS; import static org.xmind.core.internal.zip.ArchiveConstants.REVISIONS_XML; import static org.xmind.core.internal.zip.ArchiveConstants.STYLES_XML; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.zip.ZipOutputStream; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xmind.core.Core; import org.xmind.core.CoreException; import org.xmind.core.IAdaptable; import org.xmind.core.IChecksumStream; import org.xmind.core.IEncryptionData; import org.xmind.core.IFileEntry; import org.xmind.core.IManifest; import org.xmind.core.IMeta; import org.xmind.core.IRevision; import org.xmind.core.IRevisionManager; import org.xmind.core.internal.InternalCore; import org.xmind.core.internal.security.Crypto; import org.xmind.core.internal.zip.ArchiveConstants; import org.xmind.core.internal.zip.ZipStreamOutputTarget; import org.xmind.core.io.DirectoryOutputTarget; import org.xmind.core.io.ICloseableOutputTarget; import org.xmind.core.io.IInputSource; import org.xmind.core.io.IOutputTarget; import org.xmind.core.marker.IMarkerSheet; import org.xmind.core.style.IStyleSheet; import org.xmind.core.util.DOMUtils; /** * @author Frank Shaka * @deprecated Use <code>Core.getSerializerFactory().newSerializer()</code> */ @Deprecated public class WorkbookSaver { private static class WorkbookSaveSession { /** * The workbook to save. */ private final WorkbookImpl workbook; /** * The multi-entry output target to save to. */ private final IOutputTarget target; /** * Entry paths that have been saved. */ private Set<String> savedEntries = new HashSet<String>(); /** * DOM transformer factory, lazy created. */ private TransformerFactory transformerFactory = null; /** * Constructs a new WorkbookSaveSession. * * @param workbook * @param target */ public WorkbookSaveSession(WorkbookImpl workbook, IOutputTarget target) { this.workbook = workbook; this.target = target; } /** * The main saving process. * * @throws IOException * @throws CoreException */ public synchronized void save() throws IOException, CoreException { try { try { try { saveMeta(); } finally { try { saveContent(); } finally { try { saveMarkerSheet(); } finally { try { saveStyleSheet(); } finally { try { saveComments(); } finally { try { if (!workbook .isSkipRevisionsWhenSaving()) { saveRevisions(); } } finally { try { copyOtherStaff(); } finally { saveManifest(); } } } } } } } } finally { clearEncryptionData(); } } finally { if (target instanceof ICloseableOutputTarget) { ((ICloseableOutputTarget) target).close(); } } } private void saveManifest() throws IOException, CoreException { saveDOM(workbook.getManifest(), target, MANIFEST_XML); } private void saveStyleSheet() throws IOException, CoreException { IStyleSheet styleSheet = workbook.getStyleSheet(); if (!styleSheet.isEmpty()) { saveDOM(styleSheet, target, STYLES_XML); } } private void saveMarkerSheet() throws IOException, CoreException { IMarkerSheet markerSheet = workbook.getMarkerSheet(); if (!markerSheet.isEmpty()) { saveDOM(markerSheet, target, PATH_MARKER_SHEET); } } private void saveComments() throws IOException, CoreException { } private void saveRevisions() throws IOException, CoreException { Iterator<Element> sheets = DOMUtils.childElementIterByTag( workbook.getWorkbookElement(), TAG_SHEET); while (sheets.hasNext()) { Element sheetEle = sheets.next(); String sheetId = sheetEle.getAttribute(ATTR_ID); IRevisionManager manager = workbook.getRevisionRepository() .getRevisionManager(sheetId, IRevision.SHEET); String path = PATH_REVISIONS + sheetId + "/" + REVISIONS_XML; //$NON-NLS-1$ saveDOM(manager, target, path); } } private void saveContent() throws IOException, CoreException { saveDOM(workbook, target, CONTENT_XML); } private void saveMeta() throws IOException, CoreException { IMeta meta = workbook.getMeta(); // meta.setValue(IMeta.CREATOR_NAME, workbook.getCurrentCreatorName()); // meta.setValue(IMeta.CREATOR_VERSION, workbook.getCurrentCreatorVersion()); saveDOM(meta, target, META_XML); } private void copyOtherStaff() throws IOException, CoreException { IInputSource source = workbook.getTempStorage().getInputSource(); IManifest manifest = workbook.getManifest(); for (IFileEntry entry : manifest.getFileEntries()) { if (!entry.isDirectory()) { String entryPath = entry.getPath(); if (shouldSaveEntry(entryPath)) { copyEntry(source, target, entryPath); markSaved(entryPath); } } } } private synchronized void copyEntry(IInputSource source, IOutputTarget target, String entryPath) throws IOException, CoreException { InputStream in = getInputStream(source, entryPath); if (in == null) { Core.getLogger().log( "Save workbook: failed to copy entry, input stream not avaiable: " //$NON-NLS-1$ + entryPath); return; // Entry source not found. } try { long time = source.getEntryTime(entryPath); if (time >= 0) { target.setEntryTime(entryPath, time); } OutputStream out = getOutputStream(target, entryPath); try { int numBytes; byte[] byteBuffer = new byte[4096]; while ((numBytes = in.read(byteBuffer)) > 0) { out.write(byteBuffer, 0, numBytes); } recordChecksum(entryPath, out); } finally { out.close(); } } finally { in.close(); } } private boolean shouldSaveEntry(String entryPath) { return entryPath != null && !"".equals(entryPath) //$NON-NLS-1$ && !MANIFEST_XML.equals(entryPath) && !hasBeenSaved(entryPath) && !(workbook.isSkipRevisionsWhenSaving() && entryPath .startsWith(ArchiveConstants.PATH_REVISIONS)); } private void clearEncryptionData() { for (IFileEntry entry : workbook.getManifest().getFileEntries()) { entry.deleteEncryptionData(); } } private void saveDOM(IAdaptable domAdapter, IOutputTarget target, String entryPath) throws IOException, CoreException { Node node = (Node) domAdapter.getAdapter(Node.class); if (node == null) { Core.getLogger() .log("SaveWorkbook: No DOM node available for entry: " //$NON-NLS-1$ + entryPath); return; } if (transformerFactory == null) { try { transformerFactory = TransformerFactory.newInstance(); } catch (TransformerFactoryConfigurationError error) { throw new CoreException( Core.ERROR_FAIL_ACCESS_XML_TRANSFORMER, "Failed to obtain XML transformer factory.", error); //$NON-NLS-1$ } } Transformer transformer; try { transformer = transformerFactory.newTransformer(); } catch (TransformerConfigurationException error) { throw new CoreException(Core.ERROR_FAIL_ACCESS_XML_TRANSFORMER, "Failed to create XML transformer for DOM entry '" //$NON-NLS-1$ + entryPath + "'.", //$NON-NLS-1$ error); } OutputStream out = getOutputStream(target, entryPath); try { transformer.transform(new DOMSource(node), new StreamResult(out)); } catch (TransformerException e) { throw new IOException(e.getLocalizedMessage(), e); } finally { out.close(); } recordChecksum(entryPath, out); markSaved(entryPath); } private void recordChecksum(String entryPath, Object checksumProvider) throws IOException { if (checksumProvider instanceof IChecksumStream) { IEncryptionData encData = workbook.getManifest() .getEncryptionData(entryPath); if (encData != null && encData.getChecksumType() != null) { String checksum = ((IChecksumStream) checksumProvider) .getChecksum(); if (checksum != null) { encData.setAttribute(checksum, DOMConstants.ATTR_CHECKSUM); } } } } private InputStream getInputStream(IInputSource source, String entryPath) { if (source.hasEntry(entryPath)) { return source.getEntryStream(entryPath); } return null; } private OutputStream getOutputStream(IOutputTarget target, String entryPath) throws IOException, CoreException { OutputStream out = target.openEntryStream(entryPath); String password = workbook.getPassword(); if (password == null) return out; IFileEntry entry = workbook.getManifest().getFileEntry(entryPath); if (entry == null) return out; if (ignoresEncryption(entry, entryPath)) return out; IEncryptionData encData = entry.createEncryptionData(); return Crypto.creatOutputStream(out, true, encData, password); } private boolean ignoresEncryption(IFileEntry entry, String entryPath) { return MANIFEST_XML.equals(entryPath) || ((FileEntryImpl) entry).isIgnoreEncryption(); } private boolean hasBeenSaved(String entryPath) { return savedEntries.contains(entryPath); } private void markSaved(String entryPath) { savedEntries.add(entryPath); } } /** * The workbook to save. */ private final WorkbookImpl workbook; /** * The last target saved to. */ private IOutputTarget lastTarget; /** * (Optional) The absolute path representing a ZIP file target */ private String file; /** * Whether to skip revisions when saving. */ private boolean skipRevisions = false; /** * @param workbook * @param file */ public WorkbookSaver(WorkbookImpl workbook, String file) { super(); this.workbook = workbook; this.file = file; } /** * @return the file path */ public String getFile() { return file; } public void setFile(String file) { this.file = file; } /** * @param skipRevisions * the skipRevisions to set */ public void setSkipRevisions(boolean skipRevisions) { this.skipRevisions = skipRevisions; } public boolean isSkipRevisions() { return skipRevisions; } /** * * @throws IOException * @throws CoreException */ public synchronized void save() throws IOException, CoreException { doSave(this.lastTarget); } /** * * @param output * @throws IOException * @throws CoreException */ public synchronized void save(OutputStream output) throws IOException, CoreException { doSave(new ZipStreamOutputTarget(new ZipOutputStream(output))); } public synchronized void save(String file) throws IOException, CoreException { if (new File(file).isDirectory()) { doSave(new DirectoryOutputTarget(file)); } else { FileOutputStream fout = new FileOutputStream(file); try { ZipOutputStream stream = new ZipOutputStream(fout); try { doSave(new ZipStreamOutputTarget(stream)); } finally { stream.close(); } } finally { fout.close(); } } this.file = file; } public synchronized void save(IOutputTarget target) throws IOException, CoreException { doSave(target); this.lastTarget = target; } /** * * @param target * @throws IOException * @throws CoreException */ private synchronized void doSave(IOutputTarget target) throws IOException, CoreException { if (target == null) throw new FileNotFoundException("No target to save."); //$NON-NLS-1$ if (InternalCore.DEBUG_WORKBOOK_SAVE) Core.getLogger().log( "WorkbookSaver: About to save workbook to output target " //$NON-NLS-1$ + target.toString()); new WorkbookSaveSession(workbook, target).save(); if (InternalCore.DEBUG_WORKBOOK_SAVE) Core.getLogger().log( "WorkbookSaver: Finished saving workbook to output target " //$NON-NLS-1$ + target.toString()); } }