
org.jboss.shrinkwrap.impl.nio.file.ShrinkWrapFileSystemProvider Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source
* Copyright 2012, Red Hat Middleware LLC, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* Licensed 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.jboss.shrinkwrap.impl.nio.file;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.AccessMode;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.DirectoryStream;
import java.nio.file.DirectoryStream.Filter;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileStore;
import java.nio.file.FileSystem;
import java.nio.file.FileSystemAlreadyExistsException;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileAttributeView;
import java.nio.file.spi.FileSystemProvider;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ArchivePath;
import org.jboss.shrinkwrap.api.ArchivePaths;
import org.jboss.shrinkwrap.api.Node;
import org.jboss.shrinkwrap.api.asset.Asset;
import org.jboss.shrinkwrap.api.nio.file.MemoryNamedAsset;
import org.jboss.shrinkwrap.api.nio.file.SeekableInMemoryByteChannel;
/**
* {@link FileSystemProvider} implementation for ShrinkWrap {@link Archive}s.
*
* @author Andrew Lee Rubinger
*/
public class ShrinkWrapFileSystemProvider extends FileSystemProvider {
/**
* Logger
*/
private static final Logger log = Logger.getLogger(ShrinkWrapFileSystemProvider.class.getName());
/**
* Scheme
*/
private static final String SCHEME = "shrinkwrap";
/**
* Environment key for creating a new {@link FileSystem} denoting the archive
*/
private static final String ENV_KEY_ARCHIVE = "archive";
/**
* Open file systems, keyed by the {@link Archive#getId()}
*/
private final ConcurrentMap createdFileSystems = new ConcurrentHashMap<>();
/**
* Lock for creation of a new filesystem and other tasks which should block until this op has completed
*/
private final ReentrantLock createNewFsLock = new ReentrantLock();
/**
* {@inheritDoc}
*
* @see java.nio.file.spi.FileSystemProvider#getScheme()
*/
@Override
public String getScheme() {
return SCHEME;
}
/**
* {@inheritDoc}
*
* @see java.nio.file.spi.FileSystemProvider#newFileSystem(java.net.URI, java.util.Map)
*/
@Override
public FileSystem newFileSystem(final URI uri, final Map env) throws IOException {
// Precondition checks
if (uri == null) {
throw new IllegalArgumentException("URI must be specified");
}
if (env == null) {
throw new IllegalArgumentException("Environment must be specified");
}
// Scheme is correct?
final String scheme = uri.getScheme();
if (!scheme.equals(SCHEME)) {
throw new IllegalArgumentException(ShrinkWrapFileSystem.class.getSimpleName()
+ " supports URI with scheme " + SCHEME + " only.");
}
// Get ID of the archive
final String id = uri.getHost();
// Archive is provided?
Archive> archive = null;
final Object archiveArg = env.get(ENV_KEY_ARCHIVE);
if (archiveArg != null) {
try {
archive = Archive.class.cast(archiveArg);
// Ensure the name of the archive matches the host specified in the URI
if (!archive.getId().equals(id)) {
throw new IllegalArgumentException("Specified archive " + archive.toString()
+ " does not have name matching the host of specified URI: " + uri.toString());
}
if (log.isLoggable(Level.FINER)) {
log.finer("Found archive supplied by environment: " + archive.toString());
}
} catch (final ClassCastException cce) {
// User specified the wrong type, translate and rethrow
throw new IllegalArgumentException("Unexpected argument passed into environment under key "
+ ENV_KEY_ARCHIVE + ": " + archiveArg);
}
}
// If archive could not be found in the environment, IllegalArgumentException
if (archive == null) {
throw new IllegalArgumentException("archive must be specified in the environment under key " + ENV_KEY_ARCHIVE);
}
// Lock for compound operations on createdFileSystems
createNewFsLock.lock();
// Exists?
final ShrinkWrapFileSystem existsFs = this.createdFileSystems.get(archive.getId());
if (existsFs != null && existsFs.isOpen()) {
throw new FileSystemAlreadyExistsException("File System for URI " + uri.toString() + " already exists: "
+ existsFs.toString());
} else if (existsFs != null && !existsFs.isOpen()) {
if (log.isLoggable(Level.FINE)) {
log.fine("Attempting to create a file system for URI " + uri.toString()
+ ", and one has been made but is closed; it will be replaced by a new one.");
}
}
// Make a new FileSystem
final ShrinkWrapFileSystem newFs = new ShrinkWrapFileSystem(this, archive);
if (log.isLoggable(Level.FINE)) {
log.fine("Created new filesystem: " + newFs.toString() + " for URI " + uri.toString());
}
this.createdFileSystems.put(archive.getId(), newFs);
// Unlock, compound operations done on createdFileSystems
createNewFsLock.unlock();
// Return
return newFs;
}
/**
* {@inheritDoc}
*
* @see java.nio.file.spi.FileSystemProvider#getFileSystem(java.net.URI)
*/
@Override
public FileSystem getFileSystem(final URI uri) {
// Get open FS
final FileSystem fs = this.createdFileSystems.get(uri.getHost());
// If not already created
if (fs == null) {
throw new FileSystemNotFoundException("Could not find an open file system with URI: " + uri.toString()
+ "; try creating a new file system?");
}
// Return
return fs;
}
/**
* {@inheritDoc}
*
* @see java.nio.file.spi.FileSystemProvider#getPath(java.net.URI)
*/
@Override
public Path getPath(final URI uri) {
// Precondition checks
if (uri == null) {
throw new IllegalArgumentException("URI must be specified");
}
// ID exists? We're referencing a previously-opened archive?
final String id = uri.getHost();
ShrinkWrapFileSystem fs = null;
if (id != null && id.length() > 0) {
fs = this.createdFileSystems.get(id);
}
// Check that the file system exists
if (fs == null) {
throw new FileSystemNotFoundException("Could not find a previously-created filesystem with URI: "
+ uri.toString());
}
// Check FS is open
if (!fs.isOpen()) {
throw new FileSystemNotFoundException("File System for URI: " + uri.toString()
+ " is closed; create a new one to re-mount.");
}
final String pathComponent = uri.getPath();
final ArchivePath archivePath = ArchivePaths.create(pathComponent);
final Path path = new ShrinkWrapPath(archivePath, fs);
// Return
return path;
}
/**
* {@inheritDoc}
*
* @see java.nio.file.spi.FileSystemProvider#newFileChannel(java.nio.file.Path, java.util.Set,
* java.nio.file.attribute.FileAttribute>[])
*/
@Override
public FileChannel newFileChannel(Path path, Set extends OpenOption> options,
FileAttribute>... attrs) throws IOException {
return new ShrinkWrapFileChannel(newByteChannel(path, options, attrs));
}
/**
* {@inheritDoc}
*
* @see java.nio.file.spi.FileSystemProvider#newByteChannel(java.nio.file.Path, java.util.Set,
* java.nio.file.attribute.FileAttribute>[])
*/
@Override
public SeekableByteChannel newByteChannel(final Path path, final Set extends OpenOption> options,
final FileAttribute>... attrs) throws IOException {
// Precondition checks for null path done by NIO2 Files
// Ignore FileAttributes
// Get Archive
final Archive> archive = this.getArchive(path);
// // If overwriting isn't allowed and this path exists
final ArchivePath archivePath = ArchivePaths.create(path.toString());
// Open a new Channel (which is also a NamedAsset)
final MemoryNamedAsset channel = new MemoryNamedAsset(archivePath);
// Writing?
if (options.contains(StandardOpenOption.CREATE) || options.contains(StandardOpenOption.CREATE_NEW)
|| options.contains(StandardOpenOption.WRITE)) {
// Plug channel as Asset into the archive
if (archive.contains(channel.getName())) {
// Appending?
if (options.contains(StandardOpenOption.APPEND)) {
// Read in the existing content
channel.position(0);
final InputStream in = archive.get(archivePath).getAsset().openStream();
this.copy(in, channel);
in.close();
// Delete the existing asset and associate the channel as the new asset
archive.delete(archivePath);
archive.add(channel);
} else {
// Exception translate
throw new FileAlreadyExistsException(archivePath.get());
}
} else {
archive.add(channel);
}
// Return the channel
return channel;
}
// Else we're reading...
// Get the Node
final Node node = archive.get(archivePath);
if (node == null) {
throw new IllegalArgumentException(
"No open options have been set, and cannot read a file that does not exist");
}
// Directory?
final Asset asset = node.getAsset();
if (asset == null) {
throw new IllegalArgumentException("Cannot open a new channel to a path with existing directory: "
+ archivePath.get());
}
// Existing asset is read into the channel
final InputStream in = asset.openStream();
final SeekableByteChannel outChannel = new SeekableInMemoryByteChannel();
this.copy(in, outChannel);
// Set the position to 0 so it can be read from the beginning
outChannel.position(0);
return outChannel;
}
/**
* {@inheritDoc}
*
* @see java.nio.file.spi.FileSystemProvider#newDirectoryStream(java.nio.file.Path,
* java.nio.file.DirectoryStream.Filter)
*/
@Override
public DirectoryStream newDirectoryStream(final Path dir, final Filter super Path> filter)
throws IOException {
final FileSystem fs = dir.getFileSystem();
if (!(fs instanceof ShrinkWrapFileSystem)) {
throw new IllegalArgumentException("Expected ShrinkWrap File System for Path: " + dir.toString());
}
return new ShrinkWrapDirectoryStream(dir, (ShrinkWrapFileSystem) fs, filter);
}
/**
* {@inheritDoc}
*
* @see java.nio.file.spi.FileSystemProvider#createDirectory(java.nio.file.Path,
* java.nio.file.attribute.FileAttribute>[])
*/
@Override
public void createDirectory(final Path dir, final FileAttribute>... attrs) throws IOException {
final Archive> archive = this.getArchive(dir);
final ArchivePath parent = ArchivePaths.create(dir.toString()).getParent();
if (parent != null && !archive.contains(parent)) {
// IOException? Despite being a stupid choice for this, it's what the NIO.2 API uses.
throw new IOException("Cannot recursively create parent directories for: " + dir
+ "; instead invoke \"createDirectories\"");
}
archive.addAsDirectories(dir.toString());
if (log.isLoggable(Level.FINEST)) {
log.finest("Created directory " + dir.toString() + " on " + archive.toString());
}
}
/**
* {@inheritDoc}
*
* @see java.nio.file.spi.FileSystemProvider#delete(java.nio.file.Path)
*/
@Override
public void delete(final Path path) throws IOException {
assert path != null : "Path must be specified";
final Archive> archive = this.getArchive(path);
final String pathString = path.toString();
if (!archive.contains(pathString)) {
throw new NoSuchFileException(path + " does not exist in " + archive);
}
final boolean isDirectory = archive.get(pathString).getAsset() == null;
// Directory?
if (isDirectory) {
// Check empty?
if (!archive.getContent(new org.jboss.shrinkwrap.api.Filter() {
@Override
public boolean include(final ArchivePath path) {
final String filterPathString = path.get();
return filterPathString.startsWith(pathString) && !filterPathString.equals(pathString);
}
}).isEmpty()) {
throw new DirectoryNotEmptyException(pathString);
}
}
archive.delete(pathString);
if (log.isLoggable(Level.FINEST)) {
log.finest("Deleted " + path.toString() + " from " + archive.toString());
}
}
/**
* Obtains the underlying archive associated with the specified Path
*
* @param path
* @return
*/
private Archive> getArchive(final Path path) {
assert path != null : "Path must be specified";
final FileSystem fs = path.getFileSystem();
assert fs != null : "File system is null";
// Could be user error in this case, passing in a Path from another provider
if (!(fs instanceof ShrinkWrapFileSystem)) {
throw new IllegalArgumentException("This path is not associated with a "
+ ShrinkWrapFileSystem.class.getSimpleName());
}
final ShrinkWrapFileSystem swfs = (ShrinkWrapFileSystem) fs;
final Archive> archive = swfs.getArchive();
assert archive != null : "No archive associated with file system";
return archive;
}
/*
* (non-Javadoc)
*
* @see java.nio.file.spi.FileSystemProvider#copy(java.nio.file.Path, java.nio.file.Path,
* java.nio.file.CopyOption[])
*/
@Override
public void copy(Path source, Path target, CopyOption... options) throws IOException {
// TODO Auto-generated method stub
}
/**
* {@inheritDoc}
*
* @see java.nio.file.spi.FileSystemProvider#move(java.nio.file.Path, java.nio.file.Path,
* java.nio.file.CopyOption[])
*/
@Override
public void move(final Path source, final Path target, final CopyOption... options) throws IOException {
// Precondition checks
if (source == null) {
throw new IllegalArgumentException("source must be specified");
}
if (target == null) {
throw new IllegalArgumentException("target must be specified");
}
// Source exists?
if (!Files.exists(source, new LinkOption[] {})) {
throw new IllegalArgumentException("Source file doesn't exist: " + source.toString());
}
// If equal, NOOP
if (source == target) {
return;
}
final Archive> archive = this.getArchive(target);
final Node node = archive.get(target.toString());
// Copying to something already present?
if (node != null) {
final Asset asset = node.getAsset();
// Directory
if (asset == null) {
// Not empty
if (node.getChildren().size() > 0) {
throw new DirectoryNotEmptyException("Cannot move to non-empty directory: " + target.toString());
}
}
}
// Move
archive.move(source.toString(), target.toString());
}
/**
* {@inheritDoc}
*
* @see java.nio.file.spi.FileSystemProvider#isSameFile(java.nio.file.Path, java.nio.file.Path)
*/
@Override
public boolean isSameFile(final Path path1, final Path path2) throws IOException {
// Only equal if pointing to same FS
final FileSystem fs1 = path1.getFileSystem();
final FileSystem fs2 = path2.getFileSystem();
if (fs1 != fs2) {
return false;
}
// Same if the normalized form points to the same location
final String normalized1 = path1.normalize().toString();
final String normalized2 = path2.normalize().toString();
return normalized1.equals(normalized2);
}
/**
* {@inheritDoc}
*
* @see java.nio.file.spi.FileSystemProvider#isHidden(java.nio.file.Path)
*/
@Override
public boolean isHidden(final Path path) throws IOException {
// No paths are hidden
return false;
}
/**
* {@inheritDoc}
*
* @see java.nio.file.spi.FileSystemProvider#getFileStore(java.nio.file.Path)
*/
@Override
public FileStore getFileStore(final Path path) throws IOException {
final FileStore fileStore = path.getFileSystem().getFileStores().iterator().next();
return fileStore;
}
/**
* {@inheritDoc}
*
* @see java.nio.file.spi.FileSystemProvider#checkAccess(java.nio.file.Path, java.nio.file.AccessMode[])
*/
@Override
public void checkAccess(final Path path, final AccessMode... modes) throws IOException {
// We support READ, WRITE, and EXECUTE on everything, so long as a file exists
final Archive> archive = this.getArchive(path);
final String desired = path.toString();
if (!archive.contains(desired)) {
throw new NoSuchFileException(desired);
}
}
/**
* {@inheritDoc}
*
* @see java.nio.file.spi.FileSystemProvider#getFileAttributeView(java.nio.file.Path, java.lang.Class,
* java.nio.file.LinkOption[])
*/
@Override
public V getFileAttributeView(final Path path, final Class type,
final LinkOption... options) {
if (path == null) {
throw new IllegalArgumentException("path must be specified");
}
if (type == null) {
throw new IllegalArgumentException("type must be specified");
}
if (!type.isAssignableFrom(ShrinkWrapFileAttributeView.class)) {
// Nope, we don't support this view
return null;
}
if (!(path instanceof ShrinkWrapPath)) {
throw new IllegalArgumentException("Only " + ShrinkWrapPath.class.getSimpleName() + " is supported");
}
return type.cast(new ShrinkWrapFileAttributeView(new ShrinkWrapFileAttributes((ShrinkWrapPath) path,
getArchive(path))));
}
/**
* {@inheritDoc}
*
* @see java.nio.file.spi.FileSystemProvider#readAttributes(java.nio.file.Path, java.lang.Class,
* java.nio.file.LinkOption[])
*/
@Override
public A readAttributes(final Path path, final Class type,
final LinkOption... options) throws IOException {
if (path == null) {
throw new IllegalArgumentException("path must be specified");
}
if (type == null) {
throw new IllegalArgumentException("type must be specified");
}
if (!type.isAssignableFrom(ShrinkWrapFileAttributes.class)) {
throw new UnsupportedOperationException("Only supports " + ShrinkWrapFileAttributes.class.getSimpleName()
+ " type");
}
if (!(path instanceof ShrinkWrapPath)) {
throw new IllegalArgumentException("Only " + ShrinkWrapPath.class.getSimpleName() + " is supported");
}
final ShrinkWrapPath swPath = (ShrinkWrapPath) path;
if (!((ShrinkWrapFileSystem) swPath.getFileSystem()).getArchive().contains(path.toString())) {
throw new NoSuchFileException(path.toString());
}
final A attributes = type.cast(new ShrinkWrapFileAttributes((ShrinkWrapPath) path, getArchive(path)));
return attributes;
}
/**
* {@inheritDoc}
*
* @see java.nio.file.spi.FileSystemProvider#readAttributes(java.nio.file.Path, java.lang.String,
* java.nio.file.LinkOption[])
*/
@Override
public Map readAttributes(final Path path, final String attributes, final LinkOption... options)
throws IOException {
throw new UnsupportedOperationException("ShrinkWrap File Systems do not support attributes");
}
/**
* {@inheritDoc}
*
* @see java.nio.file.spi.FileSystemProvider#setAttribute(java.nio.file.Path, java.lang.String, java.lang.Object,
* java.nio.file.LinkOption[])
*/
@Override
public void setAttribute(final Path path, final String attribute, final Object value, final LinkOption... options)
throws IOException {
throw new UnsupportedOperationException("ShrinkWrap File Systems do not support attributes");
}
/**
* Writes the contents of the {@link InputStream} to the {@link SeekableByteChannel}
*
* @param in
* @param out
* @throws IOException
*/
private void copy(final InputStream in, final SeekableByteChannel out) throws IOException {
assert in != null : "InStream must be specified";
assert out != null : "Channel must be specified";
final byte[] backingBuffer = new byte[1024 * 4];
final ByteBuffer byteBuffer = ByteBuffer.wrap(backingBuffer);
int bytesRead = 0;
while ((bytesRead = in.read(backingBuffer, 0, backingBuffer.length)) > -1) {
// Limit to the amount we've actually read in, so we don't overflow into old data blocks
byteBuffer.limit(bytesRead);
out.write(byteBuffer);
// Position back to 0
byteBuffer.clear();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy