diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/cczip/CommonsCompressZipFileObject.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/cczip/CommonsCompressZipFileObject.java new file mode 100644 index 0000000000..58e5c075d9 --- /dev/null +++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/cczip/CommonsCompressZipFileObject.java @@ -0,0 +1,167 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.vfs2.provider.cczip; + +import java.io.InputStream; +import java.util.HashSet; + +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.vfs2.FileName; +import org.apache.commons.vfs2.FileSystemException; +import org.apache.commons.vfs2.FileType; +import org.apache.commons.vfs2.provider.AbstractFileName; +import org.apache.commons.vfs2.provider.AbstractFileObject; + +/** + * A file in a ZIP file system. + * + * @since 2.7.0 + */ +public class CommonsCompressZipFileObject extends AbstractFileObject { + + /** + * The ZipEntry. + */ + protected ZipArchiveEntry entry; + private final HashSet children = new HashSet<>(); + private FileType type; + + protected CommonsCompressZipFileObject(final AbstractFileName name, final ZipArchiveEntry entry, + final CommonsCompressZipFileSystem fs, + final boolean zipExists) throws FileSystemException { + super(name, fs); + setZipEntry(entry); + if (!zipExists) { + type = FileType.IMAGINARY; + } + } + + /** + * Sets the details for this file object. + * + * @param entry ZIP information related to this file. + */ + protected void setZipEntry(final ZipArchiveEntry entry) { + if (this.entry != null) { + return; + } + + if (entry == null || entry.isDirectory()) { + type = FileType.FOLDER; + } else { + type = FileType.FILE; + } + + this.entry = entry; + } + + /** + * Attaches a child. + *

+ * TODO: Shouldn't this method have package-only visibility? Cannot change this without breaking binary + * compatibility. + *

+ * + * @param childName The name of the child. + */ + public void attachChild(final FileName childName) { + children.add(childName.getBaseName()); + } + + /** + * Determines if this file can be written to. + * + * @return {@code true} if this file is writable, {@code false} if not. + * @throws FileSystemException if an error occurs. + */ + @Override + public boolean isWriteable() throws FileSystemException { + return false; + } + + /** + * Returns the file's type. + */ + @Override + protected FileType doGetType() { + return type; + } + + /** + * Lists the children of the file. + */ + @Override + protected String[] doListChildren() { + try { + if (!getType().hasChildren()) { + return null; + } + } catch (final FileSystemException e) { + // should not happen as the type has already been cached. + throw new RuntimeException(e); + } + + return children.toArray(new String[children.size()]); + } + + /** + * Returns the size of the file content (in bytes). Is only called if {@link #doGetType} returns + * {@link FileType#FILE}. + */ + @Override + protected long doGetContentSize() { + return entry.getSize(); + } + + /** + * Returns the last modified time of this file. + */ + @Override + protected long doGetLastModifiedTime() throws Exception { + return entry.getTime(); + } + + /** + * Creates an input stream to read the file content from. Is only called if {@link #doGetType} returns + * {@link FileType#FILE}. The input stream returned by this method is guaranteed to be closed before this method is + * called again. + */ + @Override + protected InputStream doGetInputStream(final int bufferSize) throws Exception { + // VFS-210: zip allows to gather an input stream even from a directory and will + // return -1 on the first read. getType should not be expensive and keeps the tests + // running + if (!getType().hasContent()) { + throw new FileSystemException("vfs.provider/read-not-file.error", getName()); + } + + return getAbstractFileSystem().getZipFile().getInputStream(entry); + } + + @Override + protected void doAttach() throws Exception { + getAbstractFileSystem().getZipFile(); + } + + @Override + protected void doDetach() throws Exception { + final CommonsCompressZipFileSystem afs = getAbstractFileSystem(); + if (!afs.isOpen()) { + afs.close(); + } + } +} diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/cczip/CommonsCompressZipFileProvider.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/cczip/CommonsCompressZipFileProvider.java new file mode 100644 index 0000000000..fee5e84f1d --- /dev/null +++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/cczip/CommonsCompressZipFileProvider.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.vfs2.provider.cczip; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import org.apache.commons.vfs2.Capability; +import org.apache.commons.vfs2.FileName; +import org.apache.commons.vfs2.FileObject; +import org.apache.commons.vfs2.FileSystem; +import org.apache.commons.vfs2.FileSystemConfigBuilder; +import org.apache.commons.vfs2.FileSystemException; +import org.apache.commons.vfs2.FileSystemOptions; +import org.apache.commons.vfs2.FileType; +import org.apache.commons.vfs2.provider.AbstractFileName; +import org.apache.commons.vfs2.provider.AbstractLayeredFileProvider; +import org.apache.commons.vfs2.provider.LayeredFileName; + +/** + * A file system provider for ZIP files. Provides read-only file systems. + * + * @since 2.7.0 + */ +public class CommonsCompressZipFileProvider extends AbstractLayeredFileProvider { + + /** + * The list of capabilities this provider supports + */ + protected static final Collection capabilities = Collections.unmodifiableCollection(Arrays.asList(Capability.GET_LAST_MODIFIED, Capability.GET_TYPE, Capability.LIST_CHILDREN, Capability.READ_CONTENT, + Capability.URI, Capability.COMPRESS, Capability.VIRTUAL)); + + public CommonsCompressZipFileProvider() { + super(); + } + + /** + * Creates a layered file system. This method is called if the file system is + * not cached. + * + * @param scheme The URI scheme. + * @param file The file to create the file system on top of. + * @return The file system. + */ + @Override + protected FileSystem doCreateFileSystem(final String scheme, final FileObject file, + final FileSystemOptions fileSystemOptions) throws FileSystemException { + final AbstractFileName rootName = new LayeredFileName(scheme, file.getName(), FileName.ROOT_PATH, + FileType.FOLDER); + return new CommonsCompressZipFileSystem(rootName, file, fileSystemOptions); + } + + @Override + public Collection getCapabilities() { + return capabilities; + } + + /** + * Return config builder. + * + * @return A config builder for ZipFileProvider. + * @see org.apache.commons.vfs2.provider.AbstractFileProvider#getConfigBuilder() + */ + @Override + public FileSystemConfigBuilder getConfigBuilder() { + return CommonsCompressZipFileSystemConfigBuilder.getInstance(); + } +} diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/cczip/CommonsCompressZipFileSystem.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/cczip/CommonsCompressZipFileSystem.java new file mode 100644 index 0000000000..42a9f7d08e --- /dev/null +++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/cczip/CommonsCompressZipFileSystem.java @@ -0,0 +1,217 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.vfs2.provider.cczip; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.LinkedList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipFile; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.vfs2.Capability; +import org.apache.commons.vfs2.FileName; +import org.apache.commons.vfs2.FileObject; +import org.apache.commons.vfs2.FileSystemException; +import org.apache.commons.vfs2.FileSystemOptions; +import org.apache.commons.vfs2.Selectors; +import org.apache.commons.vfs2.VfsLog; +import org.apache.commons.vfs2.provider.AbstractFileName; +import org.apache.commons.vfs2.provider.AbstractFileSystem; +import org.apache.commons.vfs2.provider.UriParser; + +/** + * A read-only file system for ZIP files. + * + * @since 2.7.0 + */ +public class CommonsCompressZipFileSystem extends AbstractFileSystem { + + private static final Log LOG = LogFactory.getLog(CommonsCompressZipFileSystem.class); + + private final File file; + private final Charset charset; + private ZipFile zipFile; + + /** + * Cache doesn't need to be synchronized since it is read-only. + */ + private final Map cache = new HashMap<>(); + + public CommonsCompressZipFileSystem(final AbstractFileName rootName, final FileObject parentLayer, + final FileSystemOptions fileSystemOptions) throws FileSystemException { + super(rootName, parentLayer, fileSystemOptions); + + // Make a local copy of the file + file = parentLayer.getFileSystem().replicateFile(parentLayer, Selectors.SELECT_SELF); + this.charset = CommonsCompressZipFileSystemConfigBuilder.getInstance().getCharset(fileSystemOptions); + + // Open the Zip file + if (!file.exists()) { + // Don't need to do anything + zipFile = null; + return; + } + } + + @Override + public void init() throws FileSystemException { + super.init(); + + try { + // Build the index + final List strongRef = new LinkedList<>(); + final Enumeration entries = getZipFile().getEntries(); + while (entries.hasMoreElements()) { + final ZipArchiveEntry entry = entries.nextElement(); + final AbstractFileName name = (AbstractFileName) getFileSystemManager().resolveName(getRootName(), + UriParser.encode(entry.getName())); + + // Create the file + CommonsCompressZipFileObject fileObj; + if (entry.isDirectory() && getFileFromCache(name) != null) { + fileObj = (CommonsCompressZipFileObject) getFileFromCache(name); + fileObj.setZipEntry(entry); + continue; + } + + fileObj = createZipFileObject(name, entry); + putFileToCache(fileObj); + strongRef.add(fileObj); + fileObj.holdObject(strongRef); + + // Make sure all ancestors exist + // TODO - create these on demand + CommonsCompressZipFileObject parent; + for (AbstractFileName parentName = (AbstractFileName) name + .getParent(); parentName != null; fileObj = parent, parentName = (AbstractFileName) parentName + .getParent()) { + // Locate the parent + parent = (CommonsCompressZipFileObject) getFileFromCache(parentName); + if (parent == null) { + parent = createZipFileObject(parentName, null); + putFileToCache(parent); + strongRef.add(parent); + parent.holdObject(strongRef); + } + + // Attach child to parent + parent.attachChild(fileObj.getName()); + } + } + } finally { + closeCommunicationLink(); + } + } + + protected ZipFile getZipFile() throws FileSystemException { + if (zipFile == null && this.file.exists()) { + this.zipFile = createZipFile(this.file); + } + + return zipFile; + } + + protected CommonsCompressZipFileObject createZipFileObject(final AbstractFileName name, final ZipArchiveEntry entry) + throws FileSystemException { + return new CommonsCompressZipFileObject(name, entry, this, true); + } + + protected ZipFile createZipFile(final File file) throws FileSystemException { + try { + return charset == null ? new ZipFile(file) : new ZipFile(file, charset.toString()); + } catch (final IOException ioe) { + throw new FileSystemException("vfs.provider.zip/open-zip-file.error", file, ioe); + } + } + + @Override + protected void doCloseCommunicationLink() { + // Release the zip file + try { + if (zipFile != null) { + zipFile.close(); + zipFile = null; + } + } catch (final IOException e) { + // getLogger().warn("vfs.provider.zip/close-zip-file.error :" + file, e); + VfsLog.warn(getLogger(), LOG, "vfs.provider.zip/close-zip-file.error :" + file, e); + } + } + + /** + * Returns the capabilities of this file system. + */ + @Override + protected void addCapabilities(final Collection caps) { + caps.addAll(CommonsCompressZipFileProvider.capabilities); + } + + /** + * Creates a file object. + */ + @Override + protected FileObject createFile(final AbstractFileName name) throws FileSystemException { + // This is only called for files which do not exist in the Zip file + return new CommonsCompressZipFileObject(name, null, this, false); + } + + /** + * Adds a file object to the cache. + */ + @Override + protected void putFileToCache(final FileObject file) { + cache.put(file.getName(), file); + } + + protected Charset getCharset() { + return charset; + } + + /** + * Returns a cached file. + */ + @Override + protected FileObject getFileFromCache(final FileName name) { + return cache.get(name); + } + + /** + * remove a cached file. + */ + @Override + protected void removeFileFromCache(final FileName name) { + cache.remove(name); + } + + @Override + public String toString() { + return super.toString() + " for " + file; + } + + /** + * will be called after all file-objects closed their streams. protected void notifyAllStreamsClosed() { + * closeCommunicationLink(); } + */ +} diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/cczip/CommonsCompressZipFileSystemConfigBuilder.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/cczip/CommonsCompressZipFileSystemConfigBuilder.java new file mode 100644 index 0000000000..b6a3f82471 --- /dev/null +++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/cczip/CommonsCompressZipFileSystemConfigBuilder.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.vfs2.provider.cczip; + +import java.nio.charset.Charset; + +import org.apache.commons.vfs2.FileSystem; +import org.apache.commons.vfs2.FileSystemConfigBuilder; +import org.apache.commons.vfs2.FileSystemOptions; + +/** + * @since 2.7.0 + */ +public class CommonsCompressZipFileSystemConfigBuilder extends FileSystemConfigBuilder { + + private static final String _PREFIX = CommonsCompressZipFileSystemConfigBuilder.class.getName(); + private static final CommonsCompressZipFileSystemConfigBuilder INSTANCE = new CommonsCompressZipFileSystemConfigBuilder(); + private static final String KEY_CHARSET = _PREFIX + ".charset"; + + public static final CommonsCompressZipFileSystemConfigBuilder getInstance() { + return INSTANCE; + } + + private CommonsCompressZipFileSystemConfigBuilder() { + super("zip."); + } + + public Charset getCharset(final FileSystemOptions opts) { + return (Charset) getParam(opts, KEY_CHARSET); + } + + @Override + protected Class getConfigClass() { + return CommonsCompressZipFileSystem.class; + } + + public void setCharset(final FileSystemOptions opts, final Charset charset) { + setParam(opts, KEY_CHARSET, charset); + } + +} diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/cczip/package.html b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/cczip/package.html new file mode 100644 index 0000000000..e51c9b1128 --- /dev/null +++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/cczip/package.html @@ -0,0 +1,19 @@ + + +

The Zip File Provider,use commons-compress to handle zip file.

+ diff --git a/commons-vfs2/src/main/resources/org/apache/commons/vfs2/impl/providers.xml b/commons-vfs2/src/main/resources/org/apache/commons/vfs2/impl/providers.xml index 332f2ffa6f..9dfabce5cf 100644 --- a/commons-vfs2/src/main/resources/org/apache/commons/vfs2/impl/providers.xml +++ b/commons-vfs2/src/main/resources/org/apache/commons/vfs2/impl/providers.xml @@ -23,6 +23,9 @@ + + + diff --git a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/Jira733TestCase.java b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/Jira733TestCase.java new file mode 100644 index 0000000000..f90e50e479 --- /dev/null +++ b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/Jira733TestCase.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.vfs2.provider.cczip; + +import java.io.File; + +import org.apache.commons.vfs2.FileObject; +import org.apache.commons.vfs2.FileSystemException; +import org.apache.commons.vfs2.VFS; +import org.apache.commons.vfs2.cache.OnCallRefreshFileObject; +import org.apache.commons.vfs2.function.VfsConsumer; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class Jira733TestCase { + + @After + @Before + public void reset() throws FileSystemException { + VFS.reset(); + } + + @Test + public void testZipParentLayer() throws Exception { + final File file = new File("src/test/resources/test-data/test.zip"); + final String nestedPath = "commonscompresszip:" + file.getAbsolutePath() + "!/read-tests/file1.txt"; + try (final FileObject fileObject = VFS.getManager().resolveFile(nestedPath); + final FileObject wrappedFileObject = new OnCallRefreshFileObject(fileObject)) { + // VFS.getManager().getFilesCache().close(); + Assert.assertNotNull("getParentLayer() 1", wrappedFileObject.getFileSystem().getParentLayer()); + wrappedFileObject.exists(); + wrappedFileObject.getContent(); + Assert.assertNotNull("getParentLayer() 2", wrappedFileObject.getFileSystem().getParentLayer()); + } + } + + private void testZipParentLayer(final VfsConsumer consumer) throws Exception { + final File file = new File("src/test/resources/test-data/test.zip"); + Assert.assertTrue(file.exists()); + final String nestedPath = "commonscompresszip:" + file.getAbsolutePath() + "!/read-tests/file1.txt"; + try (final FileObject fileObject = VFS.getManager().resolveFile(nestedPath); + final FileObject wrappedFileObject = new OnCallRefreshFileObject(fileObject)) { + Assert.assertTrue(fileObject instanceof CommonsCompressZipFileObject); + @SuppressWarnings({"unused", "resource"}) final CommonsCompressZipFileObject zipFileObject = (CommonsCompressZipFileObject) fileObject; + Assert.assertNotNull("getParentLayer() 1", wrappedFileObject.getFileSystem().getParentLayer()); + consumer.accept(wrappedFileObject); + Assert.assertNotNull("getParentLayer() 2", wrappedFileObject.getFileSystem().getParentLayer()); + } + } + + @Test + public void testZipParentLayer_exists() throws Exception { + testZipParentLayer(FileObject::exists); + } + + @Test + public void testZipParentLayer_exists_getContents() throws Exception { + testZipParentLayer(fileObject -> { + fileObject.exists(); + fileObject.getContent(); + }); + } + + @Test + public void testZipParentLayer_getChildren() throws Exception { + testZipParentLayer(fileObject -> fileObject.getParent().getChildren()); + } + + @Test + public void testZipParentLayer_getContents() throws Exception { + testZipParentLayer(FileObject::getContent); + } + + @Test + public void testZipParentLayer_getType() throws Exception { + testZipParentLayer(FileObject::getType); + } + + @Test + public void testZipParentLayer_isAttached() throws Exception { + testZipParentLayer(FileObject::isAttached); + } + + @Test + public void testZipParentLayer_isContentOpen() throws Exception { + testZipParentLayer(FileObject::isContentOpen); + } + + @Test + public void testZipParentLayer_isExecutable() throws Exception { + testZipParentLayer(FileObject::isExecutable); + } + + @Test + public void testZipParentLayer_isFile() throws Exception { + testZipParentLayer(FileObject::isFile); + } + + @Test + public void testZipParentLayer_isFolder() throws Exception { + testZipParentLayer(FileObject::isFolder); + } + + @Test + public void testZipParentLayer_isHidden() throws Exception { + testZipParentLayer(FileObject::isHidden); + } + + @Test + public void testZipParentLayer_isReadable() throws Exception { + testZipParentLayer(FileObject::isReadable); + } + + @Test + public void testZipParentLayer_isWriteable() throws Exception { + testZipParentLayer(FileObject::isWriteable); + } +} diff --git a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/ZipProviderWithCharsetNullTestCase.java b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/ZipProviderWithCharsetNullTestCase.java new file mode 100644 index 0000000000..14d980c7e4 --- /dev/null +++ b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/ZipProviderWithCharsetNullTestCase.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.vfs2.provider.cczip; + +import java.io.File; + +import org.apache.commons.AbstractVfsTestCase; +import org.apache.commons.vfs2.FileObject; +import org.apache.commons.vfs2.FileSystem; +import org.apache.commons.vfs2.FileSystemManager; +import org.apache.commons.vfs2.FileSystemOptions; +import org.apache.commons.vfs2.impl.DefaultFileSystemManager; +import org.apache.commons.vfs2.test.AbstractProviderTestConfig; +import org.apache.commons.vfs2.test.ProviderTestSuite; +import org.junit.Assert; + +import junit.framework.Test; + +/** + * Tests for the Zip file system. + */ +public class ZipProviderWithCharsetNullTestCase extends AbstractProviderTestConfig { + /** + * Creates the test suite for the zip file system. + */ + public static Test suite() throws Exception { + return new ProviderTestSuite(new ZipProviderWithCharsetNullTestCase(), true); + } + + /** + * Prepares the file system manager. + */ + @Override + public void prepare(final DefaultFileSystemManager manager) throws Exception { + manager.addProvider("commonscompresszip", new CommonsCompressZipFileProvider()); + manager.addExtensionMap("zip", "commonscompresszip"); + manager.addMimeTypeMap("application/zip", "commonscompresszip"); + } + + /** + * Returns the base folder for read tests. + */ + @Override + public FileObject getBaseTestFolder(final FileSystemManager manager) throws Exception { + final FileSystemOptions opts = new FileSystemOptions(); + final CommonsCompressZipFileSystemConfigBuilder builder = CommonsCompressZipFileSystemConfigBuilder.getInstance(); + // Tests null as the default. + builder.setCharset(opts, null); + + final File zipFile = AbstractVfsTestCase.getTestResource("test.zip"); + final String uri = "commonscompresszip:file:" + zipFile.getAbsolutePath() + "!/"; + final FileObject resolvedFile = manager.resolveFile(uri, opts); + final FileSystem fileSystem = resolvedFile.getFileSystem(); + Assert.assertTrue(fileSystem instanceof CommonsCompressZipFileSystem); + final CommonsCompressZipFileSystem zipFileSystem = (CommonsCompressZipFileSystem) fileSystem; + Assert.assertNull(zipFileSystem.getCharset()); + return resolvedFile; + } +} diff --git a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/ZipProviderWithCharsetTestCase.java b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/ZipProviderWithCharsetTestCase.java new file mode 100644 index 0000000000..6dacd347c4 --- /dev/null +++ b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/ZipProviderWithCharsetTestCase.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.vfs2.provider.cczip; + +import java.io.File; +import java.nio.charset.StandardCharsets; + +import org.apache.commons.AbstractVfsTestCase; +import org.apache.commons.vfs2.FileObject; +import org.apache.commons.vfs2.FileSystem; +import org.apache.commons.vfs2.FileSystemManager; +import org.apache.commons.vfs2.FileSystemOptions; +import org.apache.commons.vfs2.impl.DefaultFileSystemManager; +import org.apache.commons.vfs2.test.AbstractProviderTestConfig; +import org.apache.commons.vfs2.test.ProviderTestSuite; +import org.junit.Assert; + +import junit.framework.Test; + +/** + * Tests for the Zip file system. + */ +public class ZipProviderWithCharsetTestCase extends AbstractProviderTestConfig { + /** + * Creates the test suite for the zip file system. + */ + public static Test suite() throws Exception { + return new ProviderTestSuite(new ZipProviderWithCharsetTestCase(), true); + } + + /** + * Prepares the file system manager. + */ + @Override + public void prepare(final DefaultFileSystemManager manager) throws Exception { + manager.addProvider("commonscompresszip", new CommonsCompressZipFileProvider()); + manager.addExtensionMap("zip", "commonscompresszip"); + manager.addMimeTypeMap("application/zip", "commonscompresszip"); + } + + /** + * Returns the base folder for read tests. + */ + @Override + public FileObject getBaseTestFolder(final FileSystemManager manager) throws Exception { + final FileSystemOptions opts = new FileSystemOptions(); + final CommonsCompressZipFileSystemConfigBuilder builder = CommonsCompressZipFileSystemConfigBuilder.getInstance(); + // Tests the same charset as the default but we exercise having a Charset set. + builder.setCharset(opts, StandardCharsets.UTF_8); + + final File zipFile = AbstractVfsTestCase.getTestResource("test.zip"); + final String uri = "commonscompresszip:file:" + zipFile.getAbsolutePath() + "!/"; + final FileObject resolvedFile = manager.resolveFile(uri, opts); + final FileSystem fileSystem = resolvedFile.getFileSystem(); + Assert.assertTrue(fileSystem instanceof CommonsCompressZipFileSystem); + final CommonsCompressZipFileSystem zipFileSystem = (CommonsCompressZipFileSystem) fileSystem; + Assert.assertEquals(StandardCharsets.UTF_8, zipFileSystem.getCharset()); + return resolvedFile; + } +} diff --git a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/test/FileLockTestCase.java b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/test/FileLockTestCase.java new file mode 100644 index 0000000000..83133871c6 --- /dev/null +++ b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/test/FileLockTestCase.java @@ -0,0 +1,214 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.vfs2.provider.cczip.test; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.vfs2.FileObject; +import org.apache.commons.vfs2.FileSystemException; +import org.apache.commons.vfs2.FileSystemManager; +import org.apache.commons.vfs2.VFS; +import org.apache.commons.vfs2.util.Os; +import org.junit.Assert; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Tests https://issues.apache.org/jira/browse/VFS-291 + */ +public class FileLockTestCase { + + private FileSystemManager manager; + private File newZipFile; + + private String zipFileUri; + + private void assertDelete() { + // We do not use newZipFile in the Assert message to avoid touching it before calling delete(). + Assert.assertTrue("Could not delete file", newZipFile.delete()); + } + + private void resolveAndOpenCloseContent() throws FileSystemException { + try (final FileObject zipFileObject = manager.resolveFile(zipFileUri)) { + zipFileObject.getContent().close(); + } + } + + private void resolveAndOpenCloseInputStream() throws IOException, FileSystemException { + try (final FileObject zipFileObject = manager.resolveFile(zipFileUri)) { + zipFileObject.getContent().getInputStream().close(); + } + } + + private void resolveAndOpenReadCloseInputStream() throws IOException, FileSystemException { + try (final FileObject zipFileObject = manager.resolveFile(zipFileUri)) { + try (InputStream inputStream = zipFileObject.getContent().getInputStream()) { + readAndAssert(inputStream); + } + } + } + + private void readAndAssert(final InputStream inputStream) throws IOException { + final String string = IOUtils.toString(inputStream, "UTF-8"); + Assert.assertNotNull(string); + Assert.assertEquals("This is a test file.", string); + } + + @Before + public void setup() throws IOException { + final File zipFile = new File("src/test/resources/test-data/test.zip"); + newZipFile = File.createTempFile(getClass().getSimpleName(), ".zip"); + newZipFile.deleteOnExit(); + FileUtils.copyFile(zipFile, newZipFile); + zipFileUri = "commonscompresszip:file:" + newZipFile.getAbsolutePath() + "!/read-tests/file1.txt"; + manager = VFS.getManager(); + } + + @Ignore + public void testCannotDeleteWhileStreaming() throws Exception { + try (final FileObject zipFileObject = manager.resolveFile(zipFileUri)) { + try (InputStream inputStream = zipFileObject.getContent().getInputStream()) { + if (Os.isFamily(Os.OS_FAMILY_WINDOWS)) { + // We do not use newZipFile in the Assert message to avoid touching it before calling delete(). + Assert.assertFalse("Could not delete file", newZipFile.delete()); + } + } + } + assertDelete(); + } + + @Ignore + public void testCannotDeleteWhileStreaming2() throws Exception { + Assume.assumeTrue(Os.isFamily(Os.OS_FAMILY_WINDOWS)); + try (final FileObject zipFileObject = manager.resolveFile(zipFileUri)) { + try (InputStream inputStream = zipFileObject.getContent().getInputStream()) { + // We do not use newZipFile in the Assert message to avoid touching it before calling delete(). + Assert.assertFalse("Could not delete file", newZipFile.delete()); + } + } + } + + @Test + public void testReadStreamAfterDeleteFile() throws Exception { + + try (final FileObject zipFileObject = manager.resolveFile(zipFileUri)) { + try (InputStream inputStream = zipFileObject.getContent().getInputStream()) { + // delete file after open stream + Assert.assertTrue("Could not delete file", newZipFile.delete()); + + // read file by line,count line number + int lineNumber = 0; + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) { + while (bufferedReader.readLine() != null) { + lineNumber++; + } + } + + // check line number + Assert.assertTrue("Line number should be 1", (lineNumber == 1)); + } + } + } + + @Test + public void testResolveAndOpenCloseContent() throws Exception { + resolveAndOpenCloseContent(); + assertDelete(); + } + + @Test + public void testResolveAndOpenReadCloseInputStream() throws Exception { + resolveAndOpenReadCloseInputStream(); + assertDelete(); + } + + @Test + public void testResolveAndOpenReadCloseInputStream3() throws Exception { + resolveAndOpenReadCloseInputStream(); + resolveAndOpenReadCloseInputStream(); + resolveAndOpenReadCloseInputStream(); + assertDelete(); + } + + @Test + public void testResolveAndOpenCloseContent3() throws Exception { + resolveAndOpenCloseContent(); + resolveAndOpenCloseContent(); + resolveAndOpenCloseContent(); + + assertDelete(); + } + + /** + * This test checks whether we can modify an underlying Zip file after we have performed IO operations on files + * within it, but although we no longer have any FileObjects explicitly open. + * + * @throws Exception + */ + @Test + public void testResolveAndOpenCloseInputStream() throws Exception { + resolveAndOpenCloseInputStream(); + assertDelete(); + } + + @Test + public void testResolveAndOpenCloseInputStream3() throws Exception { + resolveAndOpenCloseInputStream(); + resolveAndOpenCloseInputStream(); + resolveAndOpenCloseInputStream(); + + assertDelete(); + } + + @Test + public void testResolveOpenCloseNestedInputStreams() throws Exception { + try (final FileObject zipFileObject = manager.resolveFile(zipFileUri)) { + try (final FileObject zipFileObject2 = manager.resolveFile(zipFileUri)) { + zipFileObject2.getContent().getInputStream().close(); + } + zipFileObject.getContent().getInputStream().close(); + } + assertDelete(); + } + + @Test + public void testReadClosedFileObject() throws Exception { + final FileObject zipFileObjectRef; + try (final FileObject zipFileObject = manager.resolveFile(zipFileUri)) { + zipFileObjectRef = zipFileObject; + try (final InputStream inputStream = zipFileObject.getContent().getInputStream()) { + readAndAssert(inputStream); + } + } + try (final InputStream inputStream = zipFileObjectRef.getContent().getInputStream()) { + readAndAssert(inputStream); + } finally { + zipFileObjectRef.close(); + } + assertDelete(); + } + +} diff --git a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/test/NestedZipTestCase.java b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/test/NestedZipTestCase.java new file mode 100644 index 0000000000..b12ab79c6d --- /dev/null +++ b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/test/NestedZipTestCase.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.vfs2.provider.cczip.test; + +import org.apache.commons.AbstractVfsTestCase; +import org.apache.commons.vfs2.FileObject; +import org.apache.commons.vfs2.FileSystemManager; +import org.apache.commons.vfs2.impl.DefaultFileSystemManager; +import org.apache.commons.vfs2.provider.cczip.CommonsCompressZipFileProvider; +import org.apache.commons.vfs2.test.AbstractProviderTestConfig; +import org.apache.commons.vfs2.test.ProviderTestSuite; + +import junit.framework.Test; + +/** + * Tests for the Zip file system, using a zip file nested inside another zip file. + */ +public class NestedZipTestCase extends AbstractProviderTestConfig { + /** + * Creates the test suite for nested zip files. + */ + public static Test suite() throws Exception { + return new ProviderTestSuite(new NestedZipTestCase(), true); + } + + /** + * Prepares the file system manager. + */ + @Override + public void prepare(final DefaultFileSystemManager manager) throws Exception { + manager.addProvider("commonscompresszip", new CommonsCompressZipFileProvider()); + manager.addExtensionMap("zip", "commonscompresszip"); + manager.addMimeTypeMap("application/zip", "commonscompresszip"); + } + + /** + * Returns the base folder for tests. + */ + @Override + public FileObject getBaseTestFolder(final FileSystemManager manager) throws Exception { + // Locate the base Zip file + final String zipFilePath = AbstractVfsTestCase.getTestResource("nested.zip").getAbsolutePath(); + final String uri = "commonscompresszip:file:" + zipFilePath + "!/test.zip"; + final FileObject zipFile = manager.resolveFile(uri); + + // Now build the nested file system + final FileObject nestedFS = manager.createFileSystem(zipFile); + return nestedFS.resolveFile("/"); + } +} diff --git a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/test/ParseXmlInZipTestCase.java b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/test/ParseXmlInZipTestCase.java new file mode 100644 index 0000000000..bace06170b --- /dev/null +++ b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/test/ParseXmlInZipTestCase.java @@ -0,0 +1,157 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.vfs2.provider.cczip.test; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.regex.Pattern; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.vfs2.FileObject; +import org.apache.commons.vfs2.FileSystemException; +import org.apache.commons.vfs2.FileSystemManager; +import org.apache.commons.vfs2.VFS; +import org.junit.Assert; +import org.junit.Test; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +/** + * Tests that we can use JAXP to parse an input stream living inside a Zip file. + */ +public class ParseXmlInZipTestCase { + + private File createTempFile() throws IOException { + final File zipFile = new File("src/test/resources/test-data/read-xml-tests.zip"); + final File newZipFile = File.createTempFile(getClass().getSimpleName(), ".zip"); + newZipFile.deleteOnExit(); + FileUtils.copyFile(zipFile, newZipFile); + return newZipFile; + } + + private DocumentBuilder newDocumentBuilder(final FileObject containerFile, final FileObject sourceFile, + final String pathToXsdInZip) throws IOException { + final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + final boolean validate = pathToXsdInZip != null; + documentBuilderFactory.setValidating(validate); + documentBuilderFactory.setNamespaceAware(true); + if (validate) { + documentBuilderFactory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage", + "http://www.w3.org/2001/XMLSchema"); + @SuppressWarnings("resource") final FileObject schema = containerFile.resolveFile(pathToXsdInZip); + if (schema.exists()) { + documentBuilderFactory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaSource", + schema.getContent().getInputStream()); + } else { + schema.close(); + throw new FileNotFoundException(schema.toString()); + } + } + DocumentBuilder documentBuilder = null; + try { + documentBuilder = documentBuilderFactory.newDocumentBuilder(); + documentBuilder.setEntityResolver(new TestEntityResolver(containerFile, sourceFile)); + } catch (final ParserConfigurationException e) { + throw new IOException("Cannot read Java Connector configuration: " + e, e); + } + documentBuilder.setErrorHandler(new TestErrorHandler(containerFile + " - " + sourceFile)); + return documentBuilder; + } + + @Test + public void testParseXmlInZip() throws IOException, SAXException { + final File newZipFile = createTempFile(); + final String xmlFilePath = "commonscompresszip:file:" + newZipFile.getAbsolutePath() + "!/read-xml-tests/file1.xml"; + final FileSystemManager manager = VFS.getManager(); + try (final FileObject zipFileObject = manager.resolveFile(xmlFilePath)) { + try (final InputStream inputStream = zipFileObject.getContent().getInputStream()) { + final Document document = newDocumentBuilder(zipFileObject, zipFileObject, null).parse(inputStream); + Assert.assertNotNull(document); + } + } + } + + @Test + public void testResolveAndParseBiggerXmlInZip() throws IOException, SAXException { + // File is > 64 bytes + // In this case, we want to make sure that the XML document does NOT fit in the internal buffer used to parse + // the XML declaration and see if that affects JAXP when it uses its "rewind" input stream. + testResolveAndParseXmlInZip("read-xml-tests/file3-bigger.xml", null); + } + + @Test + public void testResolveAndParseInvalidXml() throws IOException, SAXException { + try { + testResolveAndParseXmlInZip("read-xml-tests/name-invalid.xml", "/read-xml-tests/name.xsd"); + } catch (final SAXException e) { + final Pattern p = Pattern.compile("Invalid content was found starting with element.+FOO"); + Assert.assertTrue(p.matcher(e.toString()).find()); + } + } + + @Test + public void testResolveAndParseNotWellFormedXml() throws IOException { + try { + testResolveAndParseXmlInZip("read-xml-tests/name-not-well-formed.xml", "/read-xml-tests/name.xsd"); + } catch (final SAXException e) { + Assert.assertTrue( + e.toString().contains("XML document structures must start and end within the same entity.")); + } + } + + @Test + public void testResolveAndParseXmlInZip() throws IOException, SAXException { + // File is < 64 bytes + // In this case, we want to make sure that the XML document DOES fit in the internal buffer used to parse + // the XML declaration and see if that affects JAXP when it uses its "rewind" input stream. + testResolveAndParseXmlInZip("read-xml-tests/file1.xml", null); + } + + private void testResolveAndParseXmlInZip(final String xmlPathInZip, final String xsdPathInZip) + throws IOException, FileSystemException, SAXException { + final File newZipFile = createTempFile(); + final String zipFilePath = "commonscompresszip:file:" + newZipFile.getAbsolutePath(); + final FileSystemManager manager = VFS.getManager(); + try (final FileObject zipFileObject = manager.resolveFile(zipFilePath)) { + try (final FileObject xmlFileObject = zipFileObject.resolveFile(xmlPathInZip)) { + try (final InputStream inputStream = xmlFileObject.getContent().getInputStream()) { + final Document document = newDocumentBuilder(zipFileObject, xmlFileObject, xsdPathInZip) + .parse(inputStream); + Assert.assertNotNull(document); + } + } + } + } + + @Test + public void testResolveAndParseXmlInZipWithOneXmlSchema() throws IOException, SAXException { + testResolveAndParseXmlInZip("read-xml-tests/name-with-xsd-ref.xml", "/read-xml-tests/name.xsd"); + } + + @Test + public void testResolveAndParseXmlInZipWithTwoXmlSchema() throws IOException, SAXException { + testResolveAndParseXmlInZip("read-xml-tests/person.xml", "/read-xml-tests/person.xsd"); + } + +} diff --git a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/test/TestEntityResolver.java b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/test/TestEntityResolver.java new file mode 100644 index 0000000000..7b41b9b382 --- /dev/null +++ b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/test/TestEntityResolver.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.vfs2.provider.cczip.test; + +import java.io.File; +import java.io.IOException; +import java.net.URI; + +import org.apache.commons.vfs2.FileObject; +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * This SAX resolver opens VFS objects (FileObject, FileContent, InputStream) but does not close them. + */ +public class TestEntityResolver implements EntityResolver { + + private final FileObject containerFile; + private final FileObject sourceFile; + + public TestEntityResolver(final FileObject containerFile, final FileObject sourceFile) { + this.containerFile = containerFile; + this.sourceFile = sourceFile; + } + + @Override + public InputSource resolveEntity(final String publicId, final String systemId) throws SAXException, IOException { + // System.out.println("resolving publicId=" + publicId + ", systemId=" + systemId); + final String fileName = new File(URI.create(systemId).getPath()).getName(); + if (/* fileName.equals("person.xsd") || */fileName.equals("name.xsd") || fileName.equals("address.xsd")) { + final String path = "/read-xml-tests/" + fileName; + final FileObject xsdFileObject = sourceFile.resolveFile(path); + if (!xsdFileObject.exists()) { + System.err.println("File does not exist: " + xsdFileObject); + throw new IllegalStateException( + "Schema " + path + " not found in file " + containerFile + " parsing " + sourceFile); + } + // System.out.println("Opening input stream on " + xsdFileObject); + return new InputSource(xsdFileObject.getContent().getInputStream()); + } + return null; + } + +} diff --git a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/test/TestErrorHandler.java b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/test/TestErrorHandler.java new file mode 100644 index 0000000000..b08295d168 --- /dev/null +++ b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/test/TestErrorHandler.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.vfs2.provider.cczip.test; + +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +/** + * A simple SAX {@link ErrorHandler} for unit testing, just throws exceptions on all errors. Logs warnings to the + * standard error console. + */ +public class TestErrorHandler implements ErrorHandler { + + private final String header; + + public TestErrorHandler(final String header) { + super(); + this.header = header; + } + + @Override + public void error(final SAXParseException exception) throws SAXException { + throw new SAXException(header, exception); + } + + @Override + public void fatalError(final SAXParseException exception) throws SAXException { + throw new SAXException(header, exception); + } + + @Override + public void warning(final SAXParseException exception) throws SAXException { + System.err.println(header); + exception.printStackTrace(System.err); + + } + +} diff --git a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/test/ZipFileObjectTestCase.java b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/test/ZipFileObjectTestCase.java new file mode 100644 index 0000000000..d07eb6bf81 --- /dev/null +++ b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/test/ZipFileObjectTestCase.java @@ -0,0 +1,181 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.vfs2.provider.cczip.test; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.vfs2.FileObject; +import org.apache.commons.vfs2.FileSystemException; +import org.apache.commons.vfs2.FileSystemManager; +import org.apache.commons.vfs2.VFS; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +public class ZipFileObjectTestCase { + + private static final String NESTED_FILE_1 = "/read-xml-tests/file1.xml"; + private static final String NESTED_FILE_2 = "/read-xml-tests/file2.xml"; + + private void assertDelete(final File fileObject) { + Assert.assertTrue("Could not delete file", fileObject.delete()); + } + + private File createTempFile() throws IOException { + final File zipFile = new File("src/test/resources/test-data/read-xml-tests.zip"); + final File newZipFile = File.createTempFile(getClass().getSimpleName(), ".zip"); + newZipFile.deleteOnExit(); + FileUtils.copyFile(zipFile, newZipFile); + return newZipFile; + } + + private void getInputStreamAndAssert(final FileObject fileObject, final String expectedId) + throws FileSystemException, IOException { + readAndAssert(fileObject, fileObject.getContent().getInputStream(), expectedId); + } + + private void readAndAssert(final FileObject fileObject, final InputStream inputStream, final String expectedId) + throws IOException { + final String streamData = IOUtils.toString(inputStream, "UTF-8"); + final String fileObjectString = fileObject.toString(); + Assert.assertNotNull(fileObjectString, streamData); + Assert.assertEquals( + fileObjectString, "\r\nfoo" + expectedId + "\r\n", + streamData); + } + + /** + * Tests that when we read a file inside a file Zip and leave it open, we can still delete the Zip after we clean up + * the Zip file. + * + * @throws IOException + */ + @Test + @Ignore("Shows that leaving a stream open and not closing any resource leaves the container file locked") + public void testLeaveNestedFileOpen() throws IOException { + final File newZipFile = createTempFile(); + final FileSystemManager manager = VFS.getManager(); + try (final FileObject zipFileObject = manager.resolveFile("commonscompresszip:file:" + newZipFile.getAbsolutePath())) { + @SuppressWarnings({"resource"}) final FileObject zipFileObject1 = zipFileObject.resolveFile(NESTED_FILE_1); + getInputStreamAndAssert(zipFileObject1, "1"); + } + assertDelete(newZipFile); + } + + /** + * Tests that we can read more than one file within a Zip file, especially after closing each FileObject. + * + * @throws IOException + */ + @Test + public void testReadingFilesInZipFile() throws IOException { + final File newZipFile = createTempFile(); + final FileSystemManager manager = VFS.getManager(); + try (final FileObject zipFileObject = manager.resolveFile("commonscompresszip:file:" + newZipFile.getAbsolutePath())) { + try (final FileObject zipFileObject1 = zipFileObject.resolveFile(NESTED_FILE_1)) { + try (final InputStream inputStream = zipFileObject1.getContent().getInputStream()) { + readAndAssert(zipFileObject1, inputStream, "1"); + } + } + resolveReadAssert(zipFileObject, NESTED_FILE_2); + } + assertDelete(newZipFile); + } + + private void resolveReadAssert(final FileObject zipFileObject, final String path) + throws IOException, FileSystemException { + try (final FileObject zipFileObject2 = zipFileObject.resolveFile(path)) { + try (final InputStream inputStream = zipFileObject2.getContent().getInputStream()) { + readAndAssert(zipFileObject2, inputStream, "2"); + } + } + } + + /** + * Tests that we can get a stream from one file in a zip file, then close another file from the same zip, then + * process the initial input stream. + * + * @throws IOException + */ + @Test + public void testReadingOneAfterClosingAnotherFile() throws IOException { + final File newZipFile = createTempFile(); + final FileSystemManager manager = VFS.getManager(); + final FileObject zipFileObject1; + final InputStream inputStream1; + try (final FileObject zipFileObject = manager.resolveFile("commonscompresszip:file:" + newZipFile.getAbsolutePath())) { + // leave resources open + zipFileObject1 = zipFileObject.resolveFile(NESTED_FILE_1); + inputStream1 = zipFileObject1.getContent().getInputStream(); + } + // The zip file is "closed", but we read from the stream now. + readAndAssert(zipFileObject1, inputStream1, "1"); + // clean up + zipFileObject1.close(); + assertDelete(newZipFile); + } + + /** + * Tests that we can get a stream from one file in a zip file, then close another file from the same zip, then + * process the initial input stream. If our internal reference counting is correct, the test passes. + * + * @throws IOException + */ + @Test + public void testReadingOneAfterClosingAnotherStream() throws IOException { + final File newZipFile = createTempFile(); + final FileSystemManager manager = VFS.getManager(); + final FileObject zipFileObject1; + final InputStream inputStream1; + try (final FileObject zipFileObject = manager.resolveFile("commonscompresszip:file:" + newZipFile.getAbsolutePath())) { + // leave resources open (note that internal counters are updated) + zipFileObject1 = zipFileObject.resolveFile(NESTED_FILE_1); + inputStream1 = zipFileObject1.getContent().getInputStream(); + resolveReadAssert(zipFileObject, NESTED_FILE_2); + } + // The Zip file is "closed", but we read from the stream now, which currently fails. + // Why aren't internal counters preventing the stream from closing? + readAndAssert(zipFileObject1, inputStream1, "1"); + // clean up + zipFileObject1.close(); + assertDelete(newZipFile); + } + + /** + * Tests that we can resolve a file in a Zip file, then close the container zip, which should still let us delete + * the Zip file. + * + * @throws IOException + */ + @Test + public void testResolveNestedFileWithoutCleanup() throws IOException { + final File newZipFile = createTempFile(); + final FileSystemManager manager = VFS.getManager(); + try (final FileObject zipFileObject = manager.resolveFile("commonscompresszip:file:" + newZipFile.getAbsolutePath())) { + @SuppressWarnings({"unused", "resource"}) + // We resolve a nested file and do nothing else. + final FileObject zipFileObject1 = zipFileObject.resolveFile(NESTED_FILE_1); + } + assertDelete(newZipFile); + } +} diff --git a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/test/ZipProviderTestCase.java b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/test/ZipProviderTestCase.java new file mode 100644 index 0000000000..a9bacca3df --- /dev/null +++ b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/cczip/test/ZipProviderTestCase.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.vfs2.provider.cczip.test; + +import java.io.File; + +import org.apache.commons.AbstractVfsTestCase; +import org.apache.commons.vfs2.FileObject; +import org.apache.commons.vfs2.FileSystemManager; +import org.apache.commons.vfs2.impl.DefaultFileSystemManager; +import org.apache.commons.vfs2.provider.cczip.CommonsCompressZipFileProvider; +import org.apache.commons.vfs2.test.AbstractProviderTestConfig; +import org.apache.commons.vfs2.test.ProviderTestSuite; + +import junit.framework.Test; + +/** + * Tests for the Zip file system. + */ +public class ZipProviderTestCase extends AbstractProviderTestConfig { + /** + * Creates the test suite for the zip file system. + */ + public static Test suite() throws Exception { + return new ProviderTestSuite(new ZipProviderTestCase(), true); + } + + /** + * Prepares the file system manager. + */ + @Override + public void prepare(final DefaultFileSystemManager manager) throws Exception { + manager.addProvider("commonscompresszip", new CommonsCompressZipFileProvider()); + manager.addExtensionMap("zip", "commonscompresszip"); + manager.addMimeTypeMap("application/zip", "commonscompresszip"); + } + + /** + * Returns the base folder for read tests. + */ + @Override + public FileObject getBaseTestFolder(final FileSystemManager manager) throws Exception { + final File zipFile = AbstractVfsTestCase.getTestResource("test.zip"); + final String uri = "commonscompresszip:file:" + zipFile.getAbsolutePath() + "!/"; + return manager.resolveFile(uri); + } +}