/* ******************************************************************************
* Copyright (c) 2006-2016 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.zip.ArchiveConstants.COMMENTS_XML;
import static org.xmind.core.internal.zip.ArchiveConstants.META_XML;
import static org.xmind.core.internal.zip.ArchiveConstants.PATH_EXTENSIONS;
import static org.xmind.core.internal.zip.ArchiveConstants.PATH_MARKER_SHEET;
import static org.xmind.core.internal.zip.ArchiveConstants.STYLES_XML;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.zip.ZipInputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.xmind.core.Core;
import org.xmind.core.CoreException;
import org.xmind.core.IDeserializer;
import org.xmind.core.IFileEntry;
import org.xmind.core.IMeta;
import org.xmind.core.IRevisionRepository;
import org.xmind.core.ISheet;
import org.xmind.core.IWorkbook;
import org.xmind.core.IWorkbookExtensionManager;
import org.xmind.core.internal.AbstractSerializingBase;
import org.xmind.core.internal.compatibility.Compatibility;
import org.xmind.core.internal.zip.ArchiveConstants;
import org.xmind.core.io.ByteArrayStorage;
import org.xmind.core.io.CoreIOException;
import org.xmind.core.io.IInputSource;
import org.xmind.core.io.IStorage;
import org.xmind.core.io.InvalidChecksumException;
import org.xmind.core.util.DOMUtils;
import org.xmind.core.util.FileUtils;
import org.xmind.core.util.IProgressReporter;
import org.xml.sax.SAXException;
/**
* @author Frank Shaka
*
*/
public class DeserializerImpl extends AbstractSerializingBase
implements IDeserializer {
private WorkbookImpl workbook;
private IStorage storage;
private IInputSource inputSource;
private InputStream inputStream;
private boolean usesWorkbookStorageAsInputSource;
private ManifestImpl manifest;
private final Map<String, Document> loadedDocuments;
/**
*
*/
public DeserializerImpl() {
super();
this.workbook = null;
this.storage = new ByteArrayStorage();
this.inputSource = null;
this.inputStream = null;
this.usesWorkbookStorageAsInputSource = false;
this.manifest = null;
this.loadedDocuments = new HashMap<String, Document>();
}
/*
* (non-Javadoc)
*
* @see org.xmind.core.IDeserializer#getWorkbook()
*/
public IWorkbook getWorkbook() {
return workbook;
}
protected void setWorkbook(WorkbookImpl workbook) {
this.workbook = workbook;
}
/*
* (non-Javadoc)
*
* @see org.xmind.core.IDeserializer#setWorkbookStorage(org.xmind.core.io.
* IStorage)
*/
public void setWorkbookStorage(IStorage storage) {
if (storage == null)
throw new IllegalArgumentException("storage is null"); //$NON-NLS-1$
this.storage = storage;
}
/*
* (non-Javadoc)
*
* @see org.xmind.core.IDeserializer#getWorkbookStorage()
*/
public IStorage getWorkbookStorage() {
return this.storage;
}
/*
* (non-Javadoc)
*
* @see org.xmind.core.IDeserializer#setInputSource(org.xmind.core.io.
* IInputSource)
*/
public void setInputSource(IInputSource source) {
if (source == null)
throw new IllegalArgumentException("input source is null"); //$NON-NLS-1$
this.inputSource = source;
this.inputStream = null;
this.usesWorkbookStorageAsInputSource = false;
}
/*
* (non-Javadoc)
*
* @see org.xmind.core.IDeserializer#setInputStream(java.io.InputStream)
*/
public void setInputStream(InputStream stream) {
if (stream == null)
throw new IllegalArgumentException("input stream is null"); //$NON-NLS-1$
this.inputStream = stream;
this.inputSource = null;
this.usesWorkbookStorageAsInputSource = false;
}
/*
* (non-Javadoc)
*
* @see org.xmind.core.IDeserializer#setWorkbookStorageAsInputSource()
*/
public void setWorkbookStorageAsInputSource() {
this.usesWorkbookStorageAsInputSource = true;
this.inputSource = null;
this.inputStream = null;
}
/*
* (non-Javadoc)
*
* @see org.xmind.core.IDeserializer#hasInputSource()
*/
public boolean hasInputSource() {
return inputSource != null || inputStream != null
|| usesWorkbookStorageAsInputSource;
}
public void deserializeManifest(IProgressReporter reporter)
throws IOException, CoreException, IllegalStateException {
if (inputStream != null) {
ZipInputStream zin = new ZipInputStream(inputStream);
try {
FileUtils.extractZipFile(zin, storage.getOutputTarget());
} finally {
zin.close();
}
} else if (inputSource != null) {
FileUtils.transfer(inputSource, storage.getOutputTarget());
} else if (!usesWorkbookStorageAsInputSource) {
throw new IllegalStateException("no input source available"); //$NON-NLS-1$
}
/// load manifest.xml first to provide file entry info
Document manifestDoc = forceLoadDocumentFromEntry(
ArchiveConstants.MANIFEST_XML);
manifest = new ManifestImpl(manifestDoc, storage);
manifest.setStreamNormalizer(getEntryStreamNormalizer());
}
/*
* (non-Javadoc)
*
* @see org.xmind.core.IDeserializer#deserialize(org.xmind.core.util.
* IProgressReporter)
*/
public void deserialize(IProgressReporter reporter)
throws IOException, CoreException, IllegalStateException {
if (manifest == null)
deserializeManifest(reporter);
/// Check if it's in old format
WorkbookImpl compatibleWorkbook = Compatibility
.loadCompatibleWorkbook(this);
if (compatibleWorkbook != null) {
setWorkbook(compatibleWorkbook);
return;
}
try {
setWorkbook(loadWorkbook());
} catch (InvalidChecksumException e) {
throw new CoreException(Core.ERROR_WRONG_PASSWORD, e);
} catch (CoreIOException e) {
CoreException ce = e.getCoreException();
throw new CoreException(ce.getType(), ce.getCodeInfo(), e);
}
}
private WorkbookImpl loadWorkbook() throws IOException, CoreException {
/// load content.xml
Document workbookDoc = forceLoadDocumentFromEntry(
ArchiveConstants.CONTENT_XML);
WorkbookImpl workbook = new WorkbookImpl(workbookDoc, manifest);
Document metaDoc = forceLoadDocumentFromEntry(META_XML);
MetaImpl meta = new MetaImpl(metaDoc);
workbook.setMeta(meta);
if (meta.getValue(IMeta.CREATED_TIME) == null) {
meta.setValue(IMeta.CREATED_TIME,
NumberUtils.formatDate(System.currentTimeMillis()));
}
meta.setValue(IMeta.CREATOR_NAME, getCreatorName());
meta.setValue(IMeta.CREATOR_VERSION, getCreatorVersion());
/// load styles.xml
Document styleSheetDoc = loadDocumentFromEntry(STYLES_XML);
if (styleSheetDoc != null) {
StyleSheetImpl styleSheet = ((StyleSheetBuilderImpl) Core
.getStyleSheetBuilder()).createStyleSheet(styleSheetDoc);
styleSheet.setManifest(manifest);
workbook.setStyleSheet(styleSheet);
}
/// load markers/markerSheet.xml
Document markerSheetDoc = loadDocumentFromEntry(PATH_MARKER_SHEET);
if (markerSheetDoc != null) {
MarkerSheetImpl markerSheet = ((MarkerSheetBuilderImpl) Core
.getMarkerSheetBuilder()).createMarkerSheet(markerSheetDoc,
new WorkbookMarkerResourceProvider(workbook));
markerSheet.setManifest(manifest);
workbook.setMarkerSheet(markerSheet);
}
/// load extensions
IWorkbookExtensionManager m = ((IWorkbook) workbook)
.getAdapter(IWorkbookExtensionManager.class);
if (m instanceof WorkbookExtensionManagerImpl) {
WorkbookExtensionManagerImpl extManager = (WorkbookExtensionManagerImpl) m;
for (String provider : extManager.getProviders()) {
Document extDoc = loadDocumentFromEntry(PATH_EXTENSIONS + //
provider + ".xml"); //$NON-NLS-1$
if (extDoc != null) {
extManager.createExtension(provider, extDoc);
}
}
}
/// load comments.xml
Document commentManagerDoc = loadDocumentFromEntry(COMMENTS_XML);
if (commentManagerDoc != null) {
CommentManagerImpl commentManager = new CommentManagerImpl(workbook,
commentManagerDoc);
workbook.setCommentManager(commentManager);
}
/// initialize workbook content
for (ISheet sheet : workbook.getSheets()) {
((SheetImpl) sheet).addNotify(workbook);
}
IRevisionRepository revisionRepository = workbook
.getRevisionRepository();
for (String resourceId : revisionRepository
.getRegisteredResourceIds()) {
revisionRepository.getRegisteredRevisionManager(resourceId);
}
/// check all file entries for integrity
/// TODO FIXME do we really need this?
Iterator<IFileEntry> it = manifest.iterFileEntries();
while (it.hasNext()) {
IFileEntry e = it.next();
if (e.isDirectory() || !e.canRead())
continue;
InputStream in = e.openInputStream();
byte[] b = new byte[1024];
while (in.read(b) != -1) {
/// do nothing
}
}
return workbook;
}
protected InputStream openEntryInputStream(String entryPath)
throws IOException, CoreException {
if (manifest == null && storage == null)
throw new IllegalStateException(
"No manifest or input source available"); //$NON-NLS-1$
if (manifest != null) {
IFileEntry entry = manifest.getFileEntry(entryPath);
if (entry != null) {
if (!entry.canRead())
return null;
return entry.openInputStream();
}
}
if (storage != null) {
IInputSource source = storage.getInputSource();
if (source != null && source.hasEntry(entryPath)
&& source.isEntryAvailable(entryPath)) {
return source.openEntryStream(entryPath);
}
}
return null;
}
protected Document loadDocumentFromEntry(String entryPath)
throws IOException, CoreException {
Document cache = loadedDocuments.get(entryPath);
if (cache != null)
return cache;
InputStream stream = openEntryInputStream(entryPath);
if (stream == null)
return null;
Document document;
try {
document = loadDocumentFromStream(stream);
} catch (CoreIOException e) {
CoreException ce = e.getCoreException();
throw new CoreException(ce.getType(), ce.getCodeInfo(), e);
} catch (IOException e) {
if (hasEncryptionData(entryPath)) {
throw new CoreException(Core.ERROR_WRONG_PASSWORD, e);
}
throw e;
} catch (CoreException e) {
if (e.getType() == Core.ERROR_CANCELLATION)
throw e;
if (hasEncryptionData(entryPath)) {
throw new CoreException(Core.ERROR_WRONG_PASSWORD, e);
}
throw e;
} catch (RuntimeException e) {
/// catching any runtime exception during xml parsing
if (hasEncryptionData(entryPath)) {
throw new CoreException(Core.ERROR_WRONG_PASSWORD, e);
}
throw e;
} catch (Error e) {
/// catching any error during xml parsing
if (hasEncryptionData(entryPath)) {
throw new CoreException(Core.ERROR_WRONG_PASSWORD, e);
}
throw e;
} finally {
if (stream != null)
stream.close();
}
if (document != null) {
loadedDocuments.put(entryPath, document);
}
return document;
}
protected Document loadDocumentFromStream(InputStream stream)
throws IOException, CoreException {
try {
DocumentBuilder builder;
try {
builder = DOMUtils.getDefaultDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new CoreException(Core.ERROR_FAIL_ACCESS_XML_PARSER, e);
}
return builder.parse(stream);
} catch (SAXException e) {
throw new CoreException(Core.ERROR_FAIL_PARSING_XML, e);
} finally {
stream.close();
}
}
protected Document createDocument() throws CoreException {
DocumentBuilder builder;
try {
builder = DOMUtils.getDefaultDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new CoreException(Core.ERROR_FAIL_ACCESS_XML_PARSER, e);
}
return builder.newDocument();
}
protected Document forceLoadDocumentFromEntry(String entryPath)
throws CoreException {
Document document;
try {
document = loadDocumentFromEntry(entryPath);
} catch (IOException e) {
document = null;
}
if (document == null) {
document = createDocument();
loadedDocuments.put(entryPath, document);
}
return document;
}
public ManifestImpl getManifest() {
return manifest;
}
private boolean hasEncryptionData(String entryPath) {
if (manifest != null) {
IFileEntry entry = manifest.getFileEntry(entryPath);
return entry != null && entry.getEncryptionData() != null;
}
return false;
}
}