VFS3 User Guide

 

 

Introduction

The Virtual File System (VFS) framework is an abstraction layer designed to simplify the programmatic access to file system resources.  One of the key benefits of VFS is to hide certain file system details and allow for file system layouts that are not required to reflect a real file system.  This allows for great flexibility and makes it possible to navigate arbitrary structures (ex. archives) as though they are part of a single file system.

 

The initial layout of the VFS starts as a mirror of the underlying operating system's real file system layout. This initial layout is referred to henceforth as the root file system. Much like UNIX, the structure of the root file system can then be altered by mounting yet another virtual file system at any location on the root file system. The virtual file system being mounted can represent just about anything.

 

While an application can add its own virtual file system types, VFS3 includes support for the following:

  • Java archives (zip, jar, war, ear, etc)
  • Real File Systems (allows you map one location of the real filesystem to a different location on the root filesystem)

 

The path structure of the root file system follows the UNIX forward slash style. This prevents consuming code from having to repeatedly refer to path separator constants, and also maps well to URI/URLs. Note there are still some portability concerns to keep in mind, see the portability section for more details.

 

In addition to providing an API, the VFS also replaces the Java file URL handler. This means that java "file:" URLs will reference the VFS and not the real file system. By doing this an application using the VFS framework can interoperate with thirdparty code that uses URLs to access the file system. As an example, java security policies can work unmodified against VFS.

 

Differences from VFS2

Aside from API changes, there are a number of key semantic changes

 

Explicit Mounting

Unlike VFS2, which transparently navigated into virtual file system types, VFS3 requires explicit mounting. While transparent mounting is easy to use, the lack of a deterministic life-cycle makes it impossible to know definitely when resources created during the mount (e.g copies of archives) can be cleared. By making mounting an explicit element of the API, the user is forced to think about the life-cycle of their mount.

 

Note that code developed on JBoss AS in most cases will not have to worry about this, since the structural deployers in the deployer framework are responsible for creating/destroying the mount points of a deployment. In the cases where this is needed there is also a special Automount API in VFS3 that can be used to simplify the porting of code that expects transparent mounting.

 

State Always Represents the FS

In VFS2, virtual file state was sometimes current, and sometimes a cached snapshot. This lead to inconsistent results. In VFS3 all state on the virtual file always represents the underlying file system, just like java.io.File. Since caching rules tend to be unique to a particular usage, it is the responsibility of a higher layer of code to decide when and what to cache.

 

No File Based Roots

VFS2 had the notion of special roots that had to be configured properly in order for the system to work correctly. This notion has been removed since any virtual file that represents a directory or a mount point can act like the VFS2 definition of a root.

 

Portability Concerns

Paths

On Windows VFS paths still use UNIX style forward slashes; however, they are normalized to always qualify the drive or UNC volume. This means that on UNIX a file can be at /blah/home; whereas on Windows, assuming the default drive is c, the path will be /C:/blah/home. The non-drive-qualified path (/blah/home) can be used to locate a virtual file; however, the returned file will always be qualified version. This allows for multiple root volumes/drives to be used with VFS, and ensures that activity on the non-qualified path also is visible on the qualified version. The portion of the path that maps to the drive or UNC path can be determined by traversing all parents of the file until isRoot() is true.

 

Basic API

org.jboss.vfs.VirtualFile

Most interactions with VFS will be through the VirtualFile class.  A VirtualFile instance represents a single file or directory in in the 'virtual' file system.  There is no guarantee a VirtualFile will directly reflect a java.io.File from the real file system at any given time.  It is possible for a VirtualFile to be backed by means other than a real file system.  It is also possible for a VirtualFile to change its backing at any given time.  It is not uncommon for a VirtualFile to move from a completely in-memory state to actually be converted to a real file system resource.  An example of this would be an entry in a Zip archive.  Initially the VirtualFile will be backed by a ZipEntry object, but may be converted if a File backing based on how the VirutalFile is used.

 

The basic methods provided for identifying a VirtualFile are listed below:

    /**
     * Get the simple VF name (X.java)
     *
     * @return the simple file name
     */
    public String getName() 

    /**
     * Get the simple VF name mapped to lowercase (x.java) (used by case-insensitive filesystems like ZIP).
     *
     * @return the lowercase simple file name
     */
    public String getLowerCaseName()

    /**
     * Get the absolute VFS full path name (/xxx/yyy/foo.ear/baz.jar/org/jboss/X.java)
     *
     * @return the VFS full path name
     */
    public String getPathName()

    /**
     * Get the path name relative to a parent virtual file.  If the given virtual file is not a parent of
     * this virtual file, then an {@code IllegalArgumentException} is thrown.
     *
     * @param parent the parent virtual file
     * @return the relative path name as a string
     * @throws IllegalArgumentException if the given virtual file is not a parent of this virtual file
     */
    public String getPathNameRelativeTo(VirtualFile parent) throws IllegalArgumentException

 

VirtualFile also provides a lot of methods you would expect on a class representing a file.  The basic methods can be used to determine the state type of the file it represents.  These methods are listed below:

    /**
     * When the file was last modified
     *
     * @return the last modified time
     */
    public long getLastModified()

    /**
     * Get the size
     *
     * @return the size
     */
    public long getSize()
    /**
     * Tests whether the underlying implementation file still exists.
     *
     * @return true if the file exists, false otherwise.
     */
    public boolean exists() 

    /**
     * Determine whether the named virtual file is a plain file.
     *
     * @return {@code true} if it is a plain file, {@code false} otherwise
     */
    public boolean isFile() 

    /**
     * Determine whether the named virtual file is a directory.
     *
     * @return {@code true} if it is a directory, {@code false} otherwise
     */
    public boolean isDirectory()

 

VirtualFile provide a standard mechanism for navigating the virtual file system using a traditional parent-child relationship.  This allows both upward and downward navigation through the file system.  The navigation based methods on VirtualFile are listed below:

 

    /**
     * Get a child virtual file.  The child may or may not exist in the virtual filesystem.
     *
     * @param path the path
     *
     * @return the child
     *
     * @throws IllegalArgumentException if the path is null
     */
    public VirtualFile getChild(String path)

    /**
     * Get a {@code VirtualFile} which represents the parent of this instance.
     *
     * @return the parent or {@code null} if there is no parent
     */
    public VirtualFile getParent()

    /**
     * Get the all the parent files of this virtual file from this file to the root.  The leafmost file will be at the
     * start of the array, and the rootmost will be at the end.
     *
     * @return the array of parent files
     */
    public VirtualFile[] getParentFiles() 

    /**
     * Get the all the parent files of this virtual file from this file to the root as a list.  The leafmost file will be
     * at the start of the list, and the rootmost will be at the end.
     *
     * @return the list of parent files
     */
    public List<VirtualFile> getParentFileList()

    /**
     * Get the children.  This is the combined list of real children within this directory, as well as virtual children
     * created by submounts.
     *
     * @return the children
     */
    public List<VirtualFile> getChildren()

    /**
     * Get the children
     *
     * @param filter to filter the children
     *
     * @return the children
     *
     * @throws IOException for any problem accessing the virtual file system
     * @throws IllegalStateException if the file is closed or it is a leaf node
     */
    public List<VirtualFile> getChildren(VirtualFileFilter filter) throws IOException

    /**
     * Get all the children recursively<p>
     * <p/>
     * This always uses {@link VisitorAttributes#RECURSE}
     *
     * @return the children
     *
     * @throws IOException for any problem accessing the virtual file system
     * @throws IllegalStateException if the file is closed
     */
    public List<VirtualFile> getChildrenRecursively() throws IOException

    /**
     * Get all the children recursively<p>
     * <p/>
     * This always uses {@link VisitorAttributes#RECURSE}
     *
     * @param filter to filter the children
     *
     * @return the children
     *
     * @throws IOException for any problem accessing the virtual file system
     * @throws IllegalStateException if the file is closed or it is a leaf node
     */
    public List<VirtualFile> getChildrenRecursively(VirtualFileFilter filter) throws IOException

 

It is also possible to read and manipulate a VirtualFile.  Below are the methods used for reading and manipulating VirtualFiles:

 

    /**
     * Access the file contents.
     *
     * @return an InputStream for the file contents.
     *
     * @throws IOException for any error accessing the file system
     */
    public InputStream openStream() throws IOException

    /**
     * Delete this virtual file
     *
     * @return {@code true} if file was deleted
     */
    public boolean delete()

    /**
     * Get a physical file for this virtual file.  Depending on the underlying file system type, this may simply return
     * an already-existing file; it may create a copy of a file; or it may reuse a preexisting copy of the file.
     * Furthermore, the retured file may or may not have any relationship to other files from the same or any other
     * virtual directory.
     *
     * @return the physical file
     *
     * @throws IOException if an I/O error occurs while producing the physical file
     */
    public File getPhysicalFile() throws IOException

 

VirtualFile also provides a mechanism to get an externalized format for referencing a VirtualFile path.  Below are the methods for retrieving externalized references:

    /**
     * Get file's current URL.  <b>Note:</b> if this VirtualFile refers to a directory <b>at the time of this
     * method invocation</b>, a trailing slash will be appended to the URL; this means that invoking
     * this method may require a filesystem access, and in addition, may not produce consistent results
     * over time.
     *
     * @return the current url
     *
     * @throws MalformedURLException if the URL is somehow malformed
     * @see VirtualFile#asDirectoryURL()
     * @see VirtualFile#asFileURL()
     */
    public URL toURL() throws MalformedURLException

    /**
     * Get file's current URI.  <b>Note:</b> if this VirtualFile refers to a directory <b>at the time of this
     * method invocation</b>, a trailing slash will be appended to the URI; this means that invoking
     * this method may require a filesystem access, and in addition, may not produce consistent results
     * over time.
     *
     * @return the current uri
     *
     * @throws URISyntaxException if the URI is somehow malformed
     * @see VirtualFile#asDirectoryURI()
     * @see VirtualFile#asFileURI()
     */
    public URI toURI() throws URISyntaxException 
    
   /**
    * Get file's URL as a directory.  There will always be a trailing {@code "/"} character.
    *
    * @return the url
    *
    * @throws MalformedURLException if the URL is somehow malformed
    */
    public URL asDirectoryURL() throws MalformedURLException 

   /**
    * Get file's URI as a directory.  There will always be a trailing {@code "/"} character.
    *
    * @return the uri
    *
    * @throws URISyntaxException if the URI is somehow malformed
    */
    public URI asDirectoryURI() throws URISyntaxException 

   /**
    * Get file's URL as a file.  There will be no trailing {@code "/"} character unless this {@code VirtualFile}
    * represents a root.
    *
    * @return the url
    *
    * @throws MalformedURLException if the URL is somehow malformed
    */
    public URL asFileURL() throws MalformedURLException 

   /**
    * Get file's URI as a file.  There will be no trailing {@code "/"} character unless this {@code VirtualFile}
    * represents a root.
    *
    * @return the url
    *
    * @throws URISyntaxException if the URI is somehow malformed
    */
    public URI asFileURI() throws URISyntaxException

org.jboss.vfs.VFS

The VFS class is the primary staring point for VFS access.  VFS provides methods for getting instances of VirtualFile from various formats as described in the method signatures below:

    /**
     * Find a virtual file.
     *
     * @param url the URL whose path component is the child path
     *
     * @return the child
     *
     * @throws IllegalArgumentException if the path is null
     * @throws java.net.URISyntaxException for any uri error
     */
    public static VirtualFile getChild(URL url) throws URISyntaxException 

    /**
     * Find a virtual file.
     *
     * @param uri the URI whose path component is the child path
     *
     * @return the child
     *
     * @throws IllegalArgumentException if the path is null
     */
    public static VirtualFile getChild(URI uri)

    /**
     *
     * @param path the child path
     *
     * @return the child
     *
     * @throws IllegalArgumentException if the path is null
     */
    public static VirtualFile getChild(String path)

    /**
     * Get the children
     *
     * @return the children
     *
     * @throws IOException for any problem accessing the virtual file system
     */
    public static List<VirtualFile> getChildren() throws IOException

    /**
     * Get the children
     *
     * @param filter to filter the children
     *
     * @return the children
     *
     * @throws IOException for any problem accessing the virtual file system
     */
    public static List<VirtualFile> getChildren(VirtualFileFilter filter) throws IOException

    /**
     * Get all the children recursively<p>
     * <p/>
     * This always uses {@link VisitorAttributes#RECURSE}
     *
     * @return the children
     *
     * @throws IOException for any problem accessing the virtual file system
     */
    public static List<VirtualFile> getChildrenRecursively() throws IOException

    /**
     * Get all the children recursively<p>
     * <p/>
     * This always uses {@link VisitorAttributes#RECURSE}
     *
     * @param filter to filter the children
     *
     * @return the children
     *
     * @throws IOException for any problem accessing the virtual file system
     */
    public static List<VirtualFile> getChildrenRecursively(VirtualFileFilter filter) throws IOException

 

Beyond retrieving VirtualFile instances, the VFS class can also be used to manipulate the root file system layout.  This is done by mounting virtual file systems into the VFS at specified locations.  Mounting will be covered in more detail later in this article, but below are the mount related methods on the VFS class:

 

    /**
     * Create and mount a zip file into the filesystem, returning a single handle which will unmount and close the file
     * system when closed.
     *
     * @param zipFile the zip file to mount
     * @param mountPoint the point at which the filesystem should be mounted
     * @param tempFileProvider the temporary file provider
     *
     * @return a handle
     *
     * @throws IOException if an error occurs
     */
    public static Closeable mountZip(File zipFile, VirtualFile mountPoint, TempFileProvider tempFileProvider) throws IOException 

    /**
     * Create and mount a zip file into the filesystem, returning a single handle which will unmount and close the file
     * system when closed.
     *
     * @param zipData an input stream containing the zip data
     * @param zipName the name of the archive
     * @param mountPoint the point at which the filesystem should be mounted
     * @param tempFileProvider the temporary file provider
     *
     * @return a handle
     *
     * @throws IOException if an error occurs
     */
    public static Closeable mountZip(InputStream zipData, String zipName, VirtualFile mountPoint, TempFileProvider tempFileProvider)

    /**
     * Create and mount a zip file into the filesystem, returning a single handle which will unmount and close the file
     * system when closed.
     *
     * @param zipFile a zip file in the VFS
     * @param mountPoint the point at which the filesystem should be mounted
     * @param tempFileProvider the temporary file provider
     *
     * @return a handle
     *
     * @throws IOException if an error occurs
     */
    public static Closeable mountZip(VirtualFile zipFile, VirtualFile mountPoint, TempFileProvider tempFileProvider) throws IOException

    /**
     * Create and mount a real file system, returning a single handle which will unmount and close the filesystem when
     * closed.
     *
     * @param realRoot the real filesystem root
     * @param mountPoint the point at which the filesystem should be mounted
     *
     * @return a handle
     *
     * @throws IOException if an error occurs
     */
    public static Closeable mountReal(File realRoot, VirtualFile mountPoint) throws IOException

    /**
     * Create and mount a temporary file system, returning a single handle which will unmount and close the filesystem
     * when closed.
     *
     * @param mountPoint the point at which the filesystem should be mounted
     * @param tempFileProvider the temporary file provider
     *
     * @return a handle
     *
     * @throws IOException if an error occurs
     */
    public static Closeable mountTemp(VirtualFile mountPoint, TempFileProvider tempFileProvider) throws IOException

    /**
     * Create and mount an expanded zip file in a temporary file system, returning a single handle which will unmount and
     * close the filesystem when closed.
     *
     * @param zipFile the zip file to mount
     * @param mountPoint the point at which the filesystem should be mounted
     * @param tempFileProvider the temporary file provider
     *
     * @return a handle
     *
     * @throws IOException if an error occurs
     */
    public static Closeable mountZipExpanded(File zipFile, VirtualFile mountPoint, TempFileProvider tempFileProvider) throws IOException

    /**
     * Create and mount an expanded zip file in a temporary file system, returning a single handle which will unmount and
     * close the filesystem when closed.  The given zip data stream is closed.
     *
     * @param zipData an input stream containing the zip data
     * @param zipName the name of the archive
     * @param mountPoint the point at which the filesystem should be mounted
     * @param tempFileProvider the temporary file provider
     *
     * @return a handle
     *
     * @throws IOException if an error occurs
     */
    public static Closeable mountZipExpanded(InputStream zipData, String zipName, VirtualFile mountPoint, TempFileProvider tempFileProvider) throws IOException

    /**
     * Create and mount an expanded zip file in a temporary file system, returning a single handle which will unmount and
     * close the filesystem when closed.  The given zip data stream is closed.
     *
     * @param zipFile a zip file in the VFS
     * @param mountPoint the point at which the filesystem should be mounted
     * @param tempFileProvider the temporary file provider
     *
     * @return a handle
     *
     * @throws IOException if an error occurs
     */
    public static Closeable mountZipExpanded(VirtualFile zipFile, VirtualFile mountPoint, TempFileProvider tempFileProvider) throws IOException

 

The VFS class also provides methods for traversing the virtual file system using VirtualFileVisitor instances.  More detail on VirtualFileVisitor usage will be covered later in the article, but below is the primary method on the VFS class:

 

    /**
     * Visit the virtual file system from the root
     *
     * @param visitor the visitor
     *
     * @throws IOException for any problem accessing the VFS
     * @throws IllegalArgumentException if the visitor is null
     */
    public static void visit(VirtualFileVisitor visitor) throws IOException

 

org.jboss.vfs.VirtualFileVisitor

A VirtualFileVisitor provides a standard mechanism for traversing the VFS.  Being able to traverse the virtual file

 

 

system easily and efficiently using a visitor reduces the amount of logic required to perform common file system tasks.  A VirtaulFileVisitor implementation is required to provide VisitorAttributes and logic to perform given a single VirtualFile.  The VirtualFileVisitor is combined with VirtualFileFilters (covered next) to provide a powerful mechanism for finding files based on some kind of criteria.  Below are the VirtualFileVistor methods:

/**
 * Get the search attributes for this visitor
 *
 * @return the attributes
 */
VisitorAttributes getAttributes();

/**
 * Visit a virtual file
 *
 * @param virtualFile the virtual file being visited
 */
void visit(VirtualFile virtualFile);

 

org.jboss.vfs.VisitorAttributes

VisitorAttributes are used to provide a control mechanism for VirtualFileVisitors.  A VirtualFileVisitors traversal behavior will change based on the attributes provided.  This is powerful mechanism for limiting the traversal space and add efficiency to the visitor.  Below is a listing of the standard VisitorAttributes properties:

 

/**
 * Whether to visit leaves only<p>
 * <p/>
 * Default: false
 *
 * @return the visit leaves only.
 */
public boolean isLeavesOnly() 

/**
 * Whether to recurse into the non-leaf file<p>. If there is a recurse filter then the result will by its
 * accepts(file) value.
 * <p/>
 * Default: false
 *
 * @param file the file
 *
 * @return the recurse flag.
 */
public boolean isRecurse(VirtualFile file)

/**
 * Get the recurse filter.
 *
 * @return the current recurse filter.
 */
public VirtualFileFilter getRecurseFilter() {
return recurseFilter;
}

/**
 * Whether to include the root of the visit<p>
 * <p/>
 * Default: false
 *
 * @return the includeRoot.
 */
public boolean isIncludeRoot() {
return includeRoot;
}

/**
 * Whether to ignore individual errors<p>
 * <p/>
 * Default: false
 *
 * @return the ignoreErrors.
 */
public boolean isIgnoreErrors() {
return ignoreErrors;
}

/**
 * Whether to include hidden files<p>
 * <p/>
 * Default: false
 *
 * @return the includeHidden.
 */
public boolean isIncludeHidden()

 

org.jboss.vfs.VirtualFileFilter

VirtualFileFilter provides an interface to determine whether or not to accept a VirtualFile for some operation.  The most common use of VirtualFileFilters is to determine whether or not a VirtualFile matches some criteria like name matching.  However, VirtualFileFilters can use any information provided by the VirtualFile to determine a match, and are not restricted to the VirtualFile name.  Below is the method for VirtualFileFilter:

/**
 * Match the virtual file
 *
 * @param file the virtual file
 *
 * @return true when it matches
 */
boolean accepts(VirtualFile file);

 

Basic Usage

Example: Simple File Navigation to Child

This is a basic example of navigating from a parent file down to a specific child.

 

VirtualFile homeDir = VFS.getChild("/home/bill/");
VirtualFile documents = homeDir.getChild("documents");
if(documents.exists() && documents.isDirectory()) {
    VirtualFile someFile = documents.getChild("someFile.txt");
    if(someFile.exists()) {
        System.out.printn("Some file exists");
    }
}

 

Example: Simple File Navigation to Parent

This is a basic example of navigating from a child to a desired parent.

 

VirtualFile homeDir = VFS.getChild("/home/bill/");

VirtualFile someFile = documents.getChild(stringWithPathToAFile);
if(someFile.exists()) {
    VirtualFile parent = someFile.getParent();
    while(parent != null && !parent.equals(homeDir)) {
        parent = parent.getParent();
    }
    if(parent == null) {
        System.out.printn("File is not found under" + homeDir);
    }
}

 

 

Example: List VirtualFile Children

Very basic example of getting the children of a VirtualFile.

 

VirtualFile homeDir = VFS.getChild("/home/bill/");
VirtualFile documents = homeDir.getChild("documents");
if(documents.exists() && documents.isDirectory()) {
    List<VirtualFile> documentList = documents.getChildren();
    for(VirtualFile document : documentList) {
        System.out.printn("Found document" + document);
    }
}

 

Example: Getting File Attributes

This is a basic example of getting attributes of a VirtualFile.

 

VirtualFile someFile = VFS.getChild("/home/bill/documents/someFile.txt");
if(someFile.exists() && someFile.isFile()) {
    String name = someFile.getName();
    String path = someFile.getPathName();
    int size = someFile.getSize();
    int lastMod = someFile.getLastModified();
}

 

Example: Reading a file

This is a basic example of reading the contents of a VirtualFile.

 

VirtualFile someFile = VFS.getChild("/home/bill/documents/someFile.txt");
if(someFile.exists()) {
    InputStream in = someFile.openStream();
    // Do something with the stream
}

 

Example:  Get VirtualFile for URL:

 

URL someFileUrl = classloader.getResource("/path/someFile.txt");
VirtualFile someFile = VFS.getChild(someFileUrl);

 

Example:  Get URL from VirtualFile

 

VirtualFile someFile = VFS.getChild("/home/bill/documents/someFile.txt");
URL someFileUrl = someFile.toURL();

 

FileSystem Mounting

Mounting Overview

In VFS mounting refers to the process of altering the existing file system by providing an alternate FileSystem implementation at a given VirtualFile location or "mount" point.  Once mounted the alternate FileSystem becomes responsible for servicing all file system operations from that mount point and below.  This provides the basis for a lot of the VFS functionality.  With mounting it is possible to mount an existing file into an alternate location and in essence create a symbolic link in the VFS.  It also allows even more complex behavior like mounting archive (jar,etc..) into the VFS, which can then be traversed and treated as part of the file system.

org.jboss.vfs.spi.FileSystem

The FileSystem interface is the primary means for providing the backing for the VFS.  Each FileSystem implementation must be able to support the required methods in order for the VFS to function correctly.  All calls to FileSystem instances are provided with two key pieces of information.  First it is given the VirtualFile representing the location the FileSystem is mounted in VFS.  The second piece of information is the target VirtualFile the request is for.  These to pieces of information can then be used to determine relative paths within the FileSystem and perform whatever operation is required for this FileSystem type.

 

The FileSystem interface is as follows:

   /**
    * Get a real {@code File} for the given path within this filesystem.  Some filesystem types will need to make a copy
    * in order to return this file; such copies should be cached and retained until the filesystem is closed.  Depending
    * on the file type, the real path of the returned {@code File} may or may not bear a relationship to the virtual
    * path provided; if such a relationship is required, it must be negotiated at the time the filesystem is mounted.
    *
    * @param mountPoint the mount point of the filesystem instance (guaranteed to be a parent of {@code target})
    * @param target the virtual file to act upon
    *
    * @return the file instance
    *
    * @throws IOException if an I/O error occurs
    */
   File getFile(VirtualFile mountPoint, VirtualFile target) throws IOException;

   /**
    * Open an input stream for the file at the given relative path.
    *
    * @param mountPoint the mount point of the filesystem instance (guaranteed to be a parent of {@code target})
    * @param target the virtual file to act upon
    *
    * @return the input stream
    *
    * @throws IOException if an I/O error occurs
    */
   InputStream openInputStream(VirtualFile mountPoint, VirtualFile target) throws IOException;

   /**
    * Determine whether this filesystem is read-only.  A read-only filesystem prohibits file modification or deletion.
    * It is not an error to mount a read-write filesystem within a read-only filesystem however (this operation does not
    * take place within the {@code FileSystem} implementation).
    *
    * @return {@code true} if the filesystem is read-only
    */
   boolean isReadOnly();

   /**
    * Attempt to delete a virtual file within this filesystem.
    *
    * @param mountPoint the mount point of the filesystem instance (guaranteed to be a parent of {@code target})
    * @param target the virtual file to act upon
    *
    * @return {@code true} if the file was deleted, {@code false} if it failed for any reason
    */
   boolean delete(VirtualFile mountPoint, VirtualFile target);

   /**
    * Get the size of a virtual file within this filesystem.
    *
    * @param mountPoint the mount point of the filesystem instance (guaranteed to be a parent of {@code target})
    * @param target the virtual file to act upon
    *
    * @return the size, in bytes, or 0L if the file does not exist or is a directory
    */
   long getSize(VirtualFile mountPoint, VirtualFile target);

   /**
    * Get the last modification time of a virtual file within this filesystem.
    *
    * @param mountPoint the mount point of the filesystem instance (guaranteed to be a parent of {@code target})
    * @param target the virtual file to act upon
    *
    * @return the modification time in milliseconds, or 0L if the file does not exist or if an error occurs
    */
   long getLastModified(VirtualFile mountPoint, VirtualFile target);

   /**
    * Ascertain the existance of a virtual file within this filesystem.
    *
    * @param mountPoint the mount point of the filesystem instance (guaranteed to be a parent of {@code target})
    * @param target the virtual file to act upon
    *
    * @return {@code true} if the file exists, {@code false} otherwise
    *
    * @throws IOException if an I/O error occurs
    */
   boolean exists(VirtualFile mountPoint, VirtualFile target);

   /**
    * Ascertain whether a virtual file within this filesystem is a plain file.
    *
    * @param mountPoint the mount point of the filesystem instance (guaranteed to be a parent of {@code target})
    * @param target the virtual file to act upon
    *
    * @return {@code true} if the file exists and is a plain file, {@code false} otherwise
    */
   boolean isFile(VirtualFile mountPoint, VirtualFile target);

   /**
    * Ascertain whether a virtual file within this filesystem is a directory.
    *
    * @param mountPoint the mount point of the filesystem instance (guaranteed to be a parent of {@code target})
    * @param target the virtual file to act upon
    *
    * @return {@code true} if the file exists and is a directory, {@code false} otherwise
    */
   boolean isDirectory(VirtualFile mountPoint, VirtualFile target);

   /**
    * Read a directory.  Returns all the simple path names (excluding "." and "..").  The returned list will be empty if
    * the node is not a directory.
    *
    * @param mountPoint the mount point of the filesystem instance (guaranteed to be a parent of {@code target})
    * @param target the virtual file to act upon
    *
    * @return the collection of children names
    */
   List<String> getDirectoryEntries(VirtualFile mountPoint, VirtualFile target);

   /**
    * Get the {@link CodeSigner}s for a the virtual file.
    *
    * @param mountPoint the mount point of the filesystem instance (guaranteed to be a parent of {@code target})
    * @param target the virtual file to act upon
    *
    * @return {@link CodeSigner} for the virtual file or null if not signed.
    */
   CodeSigner[] getCodeSigners(VirtualFile mountPoint, VirtualFile target);

   /**
    * Destroy this filesystem instance.  After this method is called, the filesystem may not be used in any way.  This
    * method should be called only after all mounts of this filesystem have been cleared; otherwise, VFS accesses may
    * result in {@code IOException}s.
    *
    * @throws IOException if an I/O error occurs during close
    */
   void close() throws IOException;

 

 

In order for the VFS to function correctly, there must be at least one FileSystem mounted.  Luckily this is done automatically by mounting a RootFileSystem instance.  From that point on all alterations to the file system must be done using the standard VFS.mount* methods.  Please refer to the VFS API for more information on the various mount methods.

 

Common FileSystem Types

There are three primary FileSystem types provided by VFS to support the most common requirements.  These FileSystems are described in-detail below:

org.jboss.vfs.spi.RealFileSystem

The RealFileSystem is a the most basic of file systems.  The primary focus of the RealFileSystem is to mirror the the real file system structure.  As you can imagine the required FileSystem methods simply delegate to the JDK File object. Each RealFileSystem is created with a specific File object.  The absolute path for the delegate File object does not necessarily reflect the VFS path for the VirtualFile the FileSystem is mounted at.  This allows for dynamic linking behavior in the VFS.

 

One common use of linking in the VFS is for temporary directories.  So it is possible to create a temporary directory on the real file system, and subsequently mount it anywhere in VFS to make it appear as though the files within the directory appear in some other path.  For convenience there are helper methods on VFS for mounting temporary directories in VFS.

 

For more information on mounting  RealFileSystems, please see the VFS.mountReal* and VFS.mountTemp* methods in the VFS API.

org.jboss.vfs.spi.RootFileSystem

The RootFileSystem is a special FileSystem instance that as the name describes, is used as the root of the VFS.  This FileSystem also delegates to the real file system, but does not have a base File to representing its root.  Instead, it requires any VirtualFile it is responsible for to have a valid path that can be represented as a File object.  This is required to support a multi-rooted file system like Windows.

org.jboss.vfs.spi.JavaZipFileSystem

The JavaZipFileSystem is the most common FileSystem type used for mounting.  As the name suggests, it is used to mount a Zip archive file into VFS.  JavaZipFileSystem is implemented by maintaining an instance of a JarFile object as well as a temporary directory.  When created the JavaZipFileSystem will index the entire instance of the JarFile and maintain a copy of each JarEntry.  For efficiency it does not read in the contents of the JarEntry into memory or write the contents to disk.  Whenever possible the JavaZipFileSystem attempts to use the JarEntry to support file system operations.  When this is not possible it will write a copy of the JarEntry to the temporary directory.  It is also possible to force the archive to be fully expanded at mount time, but using the VFS.mountZipExpaned methods.  This is less efficient as all files are written to the filesystem at creation, but will result in a fully populated temporary directory on the file system.

 

It is common for a JavaZipFileSystem to be mounted into the exact location of the actual archive file used.  This provides a convenient mechanism for navigating into the archive as though it were a directory and also provides more meaningful URLs to the VirtualFiles.

 

For more information on mounting  JavaZipFileSystems, please see the VFS.mountZip* methods in the VFS API.

Mounting FileSystems

All FileSystem instances must be mounted using the mount* methods on the VFS class.  These methods are the only mechanism that are capable of altering the the structure of the VFS.  All mount methods require a VirtualFile to use as the mount point for the FileSystem. Only one FileSystem can be mounted in a single VirtualFile location at a time.  So once a FileSystem has been mounted at a VirtualFile location, no other FileSystem can be mounted at that location until the original is unmounted.

Example:  Mount Existing File in an Alternate Location

This is an example of taking a real file system directory and mounting it into the VFS with an arbitrary path.

 

File homeDir = new File("/home/bill");
VirtualFile alternateDir = VFS.getChild("/some/other/path/in/vfs/billshome");

Closeable handle = VFS.mountReal(homeDir, alternateDir);

assertEquals(new File(homeDir, "someFile.txt"), alternateDir.getChild("someFile.txt").getPhysicalFile());


Example: Mount Temporary File System

This is an example of creating a temporary directory of an existing file system path that will not actually create the files under the existing path.

 

VirtualFile homeDir = VFS.getChild("/home/bill");
VirtualFile tmpDir = homeDir.getChild("tmp");

TempFileProvider provider = TempFileProvider.create("tmp", Executors.newScheduledThreadPool(2));
Closeable handle = VFS.mountTemp(tmpDir, provider);

VirtualFile tmpFile = tmpDir.getChild("tmp.txt");
... new FileOutputStream(tmpFile.getPhysicalFile());

Example: Mount Archive File

This is an example of mounting an archive into VFS.  It will use the existing VFS location for the archive as the mount point (this is not required, but is a common use case) and will not create a copy of the archive before mounting.

 

VirtualFile homeDir = VFS.getChild("/home/bill");
VirtualFile archive = homeDir.getChild("jboss-vfs.jar");
File archiveFile = archive.getPhysicalFile();

TempFileProvider provider = TempFileProvider.create("tmp", Executors.newScheduledThreadPool(2));
Closeable handle = VFS.mountZip(archiveFile, archive, provider);

assertTrue(archive.getChild("META-INF").exists());

Example: Mount Archive InputStream

This is an example of mounting an archive into VFS.  It will use the existing VFS location for the archive as the mount point and will create a copy of the File before mounting.

 

VirtualFile homeDir = VFS.getChild("/home/bill");
VirtualFile archive = homeDir.getChild("jboss-vfs.jar");
File archiveFile = archive.getPhysicalFile();

TempFileProvider provider = TempFileProvider.create("tmp", Executors.newScheduledThreadPool(2));
Closeable handle = VFS.mountZip(new FileInputStream(archiveFile), archive, provider); // This will open the stream to the archive and copy to temp location (helps prevent file locking on the original)

assertTrue(archive.getChild("META-INF").exists());

Example: Mount Archive VirtualFile

This is an example of mounting an archive into VFS.  It will use the existing VFS location for the archive as the mount point and will create a copy of the VirtualFile before mounting.

 

VirtualFile homeDir = VFS.getChild("/home/bill");
VirtualFile archive = homeDir.getChild("jboss-vfs.jar");

TempFileProvider provider = TempFileProvider.create("tmp", Executors.newScheduledThreadPool(2));
Closeable handle = VFS.mountZip(archive, archive, provider); // This will open the stream to the archive and copy to temp location (helps prevent file locking on the original)

assertTrue(archive.getChild("META-INF").exists());

Example: Mount Archive File Expanded

This is an example of mounting an archive into VFS and fully expanding its contents.  It will use the existing VFS location for the archive as the mount point (this is not required, but is a common use case).

 

VirtualFile homeDir = VFS.getChild("/home/bill");
VirtualFile archive = homeDir.getChild("jboss-vfs.jar");
File archiveFile = archive.getPhysicalFile();

TempFileProvider provider = TempFileProvider.create("tmp", Executors.newScheduledThreadPool(2));
Closeable handle = VFS.mountZipExpanded(archiveFile, archive, provider);

assertTrue(archive.getChild("META-INF").exists());

Example: Mount Archive InputStream Expanded

This is an example of mounting an archive into VFS and fully expanding its contents.  It will use the existing VFS location for the archive as the mount point (this is not required, but is a common use case).

 

VirtualFile homeDir = VFS.getChild("/home/bill");
VirtualFile archive = homeDir.getChild("jboss-vfs.jar");
File archiveFile = archive.getPhysicalFile();

TempFileProvider provider = TempFileProvider.create("tmp", Executors.newScheduledThreadPool(2));
Closeable handle = VFS.mountZipExpanded(new FileInputStream(archiveFile), archive, provider);

assertTrue(archive.getChild("META-INF").exists());

 

Example: Mount Archive VirtualFile Expanded

This is an example of mounting an archive into VFS and fully expanding its contents.  It will use the existing VFS location for the archive as the mount point (this is not required, but is a common use case).

 

VirtualFile homeDir = VFS.getChild("/home/bill");
VirtualFile archive = homeDir.getChild("jboss-vfs.jar");

TempFileProvider provider = TempFileProvider.create("tmp", Executors.newScheduledThreadPool(2));
Closeable handle = VFS.mountZipExpanded(archive, archive, provider);

assertTrue(archive.getChild("META-INF").exists());

FileSystem Resolution

Generally, once a FileSystem has been mounted in VFS it will then be used to service all FileSystem requests from the mount point and below.  However, the VFS supports unlimited nested FileSystem mounting, and a FileSystem can be mounted at any level (except the root) in the VFS, as long as there isn't an existing FileSystem mounted at the exact mount point.  This allows the path to be created that contains several nested FileSystems.  So when a VirtualFile request is made, it will start from the VirtualFile and work its way up the path until it finds the first mounted FileSystem and pass all requests to the FileSystem.  This allows new FileSystems to be mounted at any time and can cause the VFS structure to change. Below is an example of how nested FileSystems affect the VFS:

 

//----------------------------------Current VFS Structure------------------------------------------
//  /                                                                       <-- RootFileSysem
//-------------------------------------------------------------------------------------------------
VirtualFile earsDir = VFS.getChild("/ears");
File earFile = new File("/home/bill/test.ear");
VirtualFile earVF = earsDir.getChild("test.ear");
Closeable earHandle = VFS.mountZip(earFile, earVF, tempProvider);

//----------------------------------Current VFS Structure------------------------------------------
//  /                                                                       <-- RootFileSysem
//  /ears/
//  /ears/test.ear/                                                         <-- JavaZipFileSystem 1
//  /ears/test.ear/META-INF/
//  /ears/test.ear/META-INF/application.xml
//  /ears/test.ear/test.war
//-------------------------------------------------------------------------------------------------

VirtualFile warVF = earVF.getChild("test.war");
Closeable warHandle = VFS.mountZip(warVF, warVF, tempProvider);

//----------------------------------Current VFS Structure------------------------------------------
//  /                                                                       <-- RootFileSysem
//  /ears/
//  /ears/test.ear/                                                         <-- JavaZipFileSystem 1
//  /ears/test.ear/META-INF/
//  /ears/test.ear/META-INF/application.xml
//  /ears/test.ear/test.war/                                                <-- JavaZipFileSystem 2
//  /ears/test.ear/test.war/WEB-INF/
//  /ears/test.ear/test.war/WEB-INF/web.xml
//  /ears/test.ear/test.war/WEB-INF/lib/
//  /ears/test.ear/test.war/WEB-INF/lib/test.jar
//-------------------------------------------------------------------------------------------------

VirtualFile jarVF = warVF.getChild("lib").getChild("test.jar");
Closeable jarHandle = VFS.mountZip(jarVF, jarVF, tempProvider);

//----------------------------------Current VFS Structure------------------------------------------
//  /                                                                       <-- RootFileSysem
//  /ears/
//  /ears/test.ear/                                                         <-- JavaZipFileSystem 1
//  /ears/test.ear/META-INF/
//  /ears/test.ear/META-INF/application.xml
//  /ears/test.ear/test.war/                                                <-- JavaZipFileSystem 2
//  /ears/test.ear/test.war/WEB-INF/
//  /ears/test.ear/test.war/WEB-INF/web.xml
//  /ears/test.ear/test.war/WEB-INF/lib/
//  /ears/test.ear/test.war/WEB-INF/lib/test.jar/                           <-- JavaZipFileSystem 3
//  /ears/test.ear/test.war/WEB-INF/lib/test.jar/META-INF/
//  /ears/test.ear/test.war/WEB-INF/lib/test.jar/META-INF/MANIFEST.MF
//  /ears/test.ear/test.war/WEB-INF/lib/test.jar/test.class
//-------------------------------------------------------------------------------------------------

 /* For good measure */
File utilFile = new File("/home/bill/util.jar");
VirtualFile utilVF = war.getChild("lib").getChild("util.jar");
Closeable utilHandle = VFS.mountZip(utilFile, utilVF, tempProvider);

//----------------------------------Current VFS Structure------------------------------------------ 
//  /                                                                       <-- RootFileSysem
//  /ears/
//  /ears/test.ear/                                                         <-- JavaZipFileSystem 1
//  /ears/test.ear/META-INF/
//  /ears/test.ear/META-INF/application.xml
//  /ears/test.ear/test.war/                                                <-- JavaZipFileSystem 2
//  /ears/test.ear/test.war/WEB-INF/
//  /ears/test.ear/test.war/WEB-INF/web.xml
//  /ears/test.ear/test.war/WEB-INF/lib/
//  /ears/test.ear/test.war/WEB-INF/lib/test.jar/                           <-- JavaZipFileSystem 3
//  /ears/test.ear/test.war/WEB-INF/lib/test.jar/META-INF/
//  /ears/test.ear/test.war/WEB-INF/lib/test.jar/META-INF/MANIFEST.MF
//  /ears/test.ear/test.war/WEB-INF/lib/test.jar/test.class
//  /ears/test.ear/test.war/WEB-INF/lib/util.jar/                           <-- JavaZipFileSystem 4
//  /ears/test.ear/test.war/WEB-INF/lib/util.jar/META-INF/
//  /ears/test.ear/test.war/WEB-INF/lib/util.jar/META-INF/MANIFEST.MF
//  /ears/test.ear/test.war/WEB-INF/lib/util.jar/util.class
//-------------------------------------------------------------------------------------------------

//  At this point the following call would correctly delegate to JavaZipFileSystem 4
VitualFile utilClass = VFS.getChild("/ears/test.ear/test.war/WEB-INF/lib/util.jar/util.class");

//  At this point the following call would correctly delegate to JavaZipFileSystem 3
VitualFile testClass = VFS.getChild("/ears/test.ear/test.war/WEB-INF/lib/test.jar/test.class");

//  At this point the following call would correctly delegate to JavaZipFileSystem 2
VitualFile webXml = VFS.getChild("/ears/test.ear/test.war/WEB-INF/web.xml");

//  At this point the following call would correctly delegate to JavaZipFileSystem 1
VitualFile appXml = VFS.getChild("/ears/test.ear/META-INF/application.xml");

 

Mount Cleanup

It is critically important that all FileSystems are cleaned up when no longer available.  All VFS.mount* methods return an instance of java.io.Closeable referred to as a handle.  The handle returned is the mechanism used to cleanup the mount when no longer needed.  Once the close method is called on the handle, it will invoke the close method on the FileSystem to cleanup any resources used by the FileSystem.  This will often be temporary files and directories.  Along with closing the FileSystem, closing the handle will also remove the FileSystem from VFS and free up the mount point for future mounting.

 

VirtualFile archive = VFS.getChild("/home/bill/test.jar");
Closeable handle = VFS.mount(archive, archive, tempProvider);

//  Do a bunch of stuff with the JAR contents

handle.close();  // or for convenience VFSUtils.safeClose(handle);

 

Mount Leak Detection

Code which does not properly cleanup a mount point can be detected by enabling leak detection. Leak detection can be enabled by setting the jboss.vfs.leakDebugging system property to true.

Virtual File Visitors

VirtualFileVisitor Overview

The VirtualFileVisitor class was briefly described in the Basic API section of the article.  Based on the usefulness of the VFS visitors, it is import to cover it in more detail.  One major benefit of having the VFS is to be able to perform operations on file system structures without having to repeat the traversal logic all over the place.  The VirtualFileVistor allows the traversal logic to be in a single place and allows new visitors to be created by simply providing the attributes of how to control the traversal.

 

The default traversal is built into the VirtualFile class itself.  As shown in the VirutalFile API, it is possible to pass a VirtualFileVisitor into the visit method and the VirtualFile structure will take care of passing the visitor through the child structure.

 

By adjusting the VirtualFileAttributes for a visitor you can make the visitor recurse through the entire structure by providing a recurse filter.  A recurse filter is an instance of the VirtualFileFilter that will make a decision whether to recurse a directory or not.  You can also control whether the visitor visits only file or both files and directories.   Since there a number of attributes to control there are several VistorAttribute constants to choose from. The list below describes the default VisitorAttribute options:

 

org.jboss.vfs.VisitorAttributes.DEFAULT

The DEFAULT attributes will visit all children of a VirtualFile, but will not recurse to the sub-children and so on.

 

org.jboss.vfs.VisitorAttributes.LEAVES_ONLY

The LEAVES_ONLY attributes will visit only the children that are files(non-directories), but will not recurse to the sub-children and so on.

 

org.jboss.vfs.VisitorAttributes.RECURSE

The RECURSE attributes will visit all children of a VirtualFile, and will recurse to the sub-children and so on.

 

org.jboss.vfs.VisitorAttributes.RECURSE_LEAVES_ONLY

The RECURSE_LEAVES_ONLY attributes will visit only the children that are files(non-directories), and will recurse to the sub-children and so on.

 

 

There is one provided VirtualFileVisitor implementation in VFS.

org.jboss.vfs.util.FilterVirtualFileVisitor

The FilterVirtualFileVisitors basic function is to visit the VFS structure and collect a list of VirtualFiles that match a given VirtualFileFilter.

 

Along with the FilterVirtualFileVisitor there are a number of common VirtualFileFilter implementations provided. The list of provided VirtualFileFilters is described below:

 

org.jboss.vfs.util.MatchAllVirtualFileFilter

Will match all VirtualFiles it visits.

 

org.jboss.vfs.util.FileNameVirtualFileFilter

Exclude virtual file by file name and path.

 

org.jboss.vfs.util.SuffixMatchFilter

Will match any VirtualFile who's name ends in one of the provided suffixes.

 

org.jboss.vfs.util.SuffixesExcludeFilter

Will match any VirtualFile who's name does not end in one the provided suffixes.

 

Example: Find all Jar Files

 

VirtualFile earFile = VFS.getChild("/home/bill/arhive.ear");

SuffixMatchFilter jarFilter = new SuffixMatchFilter(".jar");
FilterVirtualFileVisitor visitor = new FilterVirtualFileVisitor(filter, org.jboss.vfs.VisitorAttributes.RECURSE);

earFile.visit(visitor);

List<VirtualFile> jarFiles = visitor.getMatched();

Extending VFS

As mentioned above the VFS is driven by mounting FileSystem instances.  In order to add additional functionality to VFS, additional FileSystem types are required.  Ideally this should be avoided if possible, but there may be cases where extension are needed.  All that is required the creation of a FileSystem implementation.

 

Below is an example of creating a custom FileSystem that will automatically uncompress any GZiped files based on file extension.

 

public class GzipFileSystem implements FileSystem
{
   private static SuffixMatchFilter GZIP_FILTER = new SuffixMatchFilter(".gz");
   private static final boolean NEEDS_CONVERSION = File.separatorChar != '/';

   private final File realRoot;
   private final File tempDir;

   public GzipFileSystem(File realRoot, TempDir tempDir) {
       this.realRoot = realRoot;
       this.tempDir = tempDir.getFile("contents");
   }

   public File getFile(VirtualFile mountPoint, VirtualFile target)
   {
      String relativePath = target.getPathRelativeTo(mountPoint);
      if(NEEDS_CONVERSION)
        relativePath = relativePath.replace('/', File.separatorChar);
      if (mountPoint.equals(target)) {
         return realRoot;
      } else if(GZIP_FILTER.accepts(target)) {
        File tempFile = new File(tempDir, relativePath);
        if(!tempFile.exists) {
            tempFile.getParentFile().mkdirs();
            VFSUtils.copyStreamAndClose(new GZIPInputStream(new File(realRoot, relativePath)), new FileOutputStream(tempFile));
        }
        return tempFile;
      } else {
         return new File(realRoot, relativePath);
      }   
   }

   public InputStream openInputStream(VirtualFile mountPoint, VirtualFile target) throws IOException
   {
      if(GZIP_FILTER.accepts(target))
        return new GZIPInputStream(new FileInputStream(getFile(mountPoint, target)));
      else
        return new FileInputStream(getFile(mountPoint, target));
   }

   public boolean delete(VirtualFile mountPoint, VirtualFile target) {
      return getFile(mountPoint, target).delete();
   }

   public long getSize(VirtualFile mountPoint, VirtualFile target) {
      return getFile(mountPoint, target).length();
   }

   public long getLastModified(VirtualFile mountPoint, VirtualFile target) {
      return getFile(mountPoint, target).lastModified(); 
   }

   public boolean exists(VirtualFile mountPoint, VirtualFile target) {
      return getFile(mountPoint, target).exists();
   }

   public boolean isFile(final VirtualFile mountPoint, final VirtualFile target) {
      return getFile(mountPoint, target).isFile();
   }

   public boolean isDirectory(VirtualFile mountPoint, VirtualFile target) {
      return getFile(mountPoint, target).isDirectory();
   }

   public List<String> getDirectoryEntries(VirtualFile mountPoint, VirtualFile target) {
      final String[] names = getFile(mountPoint, target).list();
      return names == null ? Collections.<String>emptyList() : Arrays.asList(names);
   }

   public void close() throws IOException {
   }
}


 

file system