HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

Do your Java applets and servlets need to read and write files stored on a server elsewhere in the network? If so, you need NFS, a fast file-access protocol that is destined to become a standard for file access over local area networks (LANS) and the Internet.

Much of your data may already be stored on NFS servers, and now you can use NFS technology to read or write remote files directly from your Java program.

Naming Files on a Network and Getting to Files with NFS
Let's start at the beginning. The NFS protocol has been used for network file access since the advent of LANs in the early 1980s. Although the protocol is normally associated with Unix clients and servers, there are implementations available for almost every computing platform.

The NFS protocol is normally built into the operating system on either the client or server side. This leads to solid performance and enables programs to get to remote file systems as if they were local.

To access files on a network server, you need to be able to name the files using a network filename that will identify the file and its server from any computer in the network. We are already familiar with network names in the form of a URL. The URL Connection classes provide URL naming to identify Web pages.

You don't have to rewrite or recompile programs to use NFS, plus NFS files are commonly named through an automounter service that creates network names such as "/net/servername/dir/file." There is also an NFS URL, "nfs://servername/path."

Getting to NFS from Java
The Java JDK provides good support for Java as a network programming language. It's easy to connect to an HTTP server via the URL Connection to read Web pages or use the JDBC classes to access database servers. RMI provides a wonderful framework for network communication between Java objects.

However, the java.io classes provide no support for URL-style naming, for example, to read a file using a FileInputStream:

InputStream in = new FileInputStream("D:\DATA\FILE");

The path name for the file must use the syntax for the underlying operating system. The OS may provide its own form of remote file access via CIFS or NFS protocols, but there is no consistent naming model that a user or programmer can rely upon. For example, the file "D:\DATA\FILE" may indeed be a remotefile since the "D" disk may be assigned to an NFS mount, but there is no reliable way to name files on other network servers as we can do with URLs and Web pages:

InputStream in = new URLConnection("http://server/page").getInputStream();

Furthermore, the JDK provides no support for remote file access. When you write a Java program that reads or writes a file, you will use the classes in java.io. With these classes you can read and write files either sequentially or randomly. You can also list directories and create, delete and rename files. The java.io classes work well until you need access to files across the network - java.io provides no support for network file access.

Table 1

The NFS team at Sun Microsystems created a set of classes that enhance thej ava.io classes to allow URL naming. The XFile classes are almost identical to the java.io classes. They take the same arguments, return the same results and throw the same exceptions. Only the class names are different: an "X" is prepended to each class name. In addition to taking the same "native" file names asjava.io, the XFile classes will handle URL names. For instance, to test if a file exists:

XFile xf = new XFile("nfs://server/a/b/c.txt");
if (xf.exists())
System.out.println("file exists");

Listing 1 demonstrates a simple program that uses the XFile classes to copy afile. It's identical to a java.io equivalent except for the "X" in front of the class name. The XFile classes provide this file copy program with a unique capability: rather than copy a file from one place to another on a local disk, the source and destination files can be named with URLs. For instance:

java xcopy nfs://server/a/b/c /tmp/x,

will copy a file from a remote NFS server to local storage, and

java xcopy nfs://server1/a/b/c nfs://server2/a/b/c

will copy a file from one NFS server to another. The XFile classes support an XFile Accessor interface that allows handlers to be written for any URL type. The NFS classes are just one type of handler for URLs that have the scheme name, "nfs:". Handlers can be written for other protocols, such as HTTP, FTP or CIFS. Handlers can also support other file system types. For instance, a "zip:" URL handler could be written that provides access to files within a zip archive.

Listing 2 is an example of an HTTP handler that implements the XFile Accessor interface for XFile handlers. This handler provides access to Web pages as if they were files. Due to the limitations of the HTTP protocol, the handler cannot provide directory listings or random access to files. HTTP servers don't normally allow clients to create files or directories without the assistance of a customized CGI script.

Listing 3 is an enhanced copy program that will copy an entire tree of files from source to destination. Since the program needs to list directories, it can be used with local files and NFS, but not HTTP or FTP.

Getting Java NFS
You can download a zip file containing the XFile package and a handler for NFSURLs from www.sun.com/webnfs. The download contains documentation, javadocs for the classes and the classes themselves. The NFS handler needs only 70 KB of bytecode, yet it implements a capable NFS client. The classes achieve good read and write performance through the use of Java threads to implement read-ahead and write-behind techniques. In addition, the handler caches file attributes, directory listings and file data. This NFS client will interoperate with many different NFS server configurations: TCP or UDP connection, NFSversions 2.0 or 3.0 and the use of fast WebNFS connection or the MOUNTprotocol.

The XFile classes provide equivalent java.io access to a variety of file system types using URL naming. The bundled NFS handler provides convenient, run-anywhere access to files on your NFS servers, which are already widely deployed on TCP/IP intranets.

The NFS protocol is destined to become a standard for file access on the Internet. The WebNFS extensions to the NFS protocol have already made Internet NFSservers accessible from Web browsers and through corporate firewalls (see www.sun.com/webnfs).

About the Author
Brent has been with Sun for 12 years in the Solaris NetworkingTechnology Group working mostly on NFS. He participated in the development of theNFS version 3.0 protocol and the design andimplementation of the Java NFS classes. He's currently co-chair of the NFS version 4.0 working group in the IETF. He can be reached at [email protected]

	

Listing 1.
 
import java.io.*; 
import com.sun.xfile.*; 
  

/* 
 * Copy a file. 
 * 
 * Notice that this program cares not whether the source 
 * and destination files are local or remote. 
 * 
 * Usage: java xcopy <src> <dst> 
 * 
 */ 
class xcopy { 
    public static void main(String av[]) { 
        try { 
            String srcFile = av[0]; 
            String dstFile = av[1]; 
  

            XFileInputStream  in  = new XFileInputStream(srcFile); 
            XFileOutputStream out = new XFileOutputStream(dstFile); 
  

            int c; 
            byte[] buf = new byte[32768]; 
  

            while ((c = in.read(buf)) > 0) 
                out.write(buf, 0, c); 
  

            in.close(); 
            out.close(); 
  

        } catch (IOException e) { 
            System.err.println(e); 
        } 
    } 
} 

Listing 2.
 
package com.sun.http; 
  

import com.sun.xfile.*; 
import java.net.URL; 
import java.net.URLConnection; 
import java.io.*; 
  

/** 
 * The XFileAccessor interface is implemented by filesystems that 
 * need to be accessed via the XFile API. 
 * 
 * @author  Brent Callaghan 
 * @version 1.0, 04/08/98 
 * @see     com.sun.xfile.XFile 
 */ 
public class XFileAccessor implements com.sun.xfile.XFileAccessor { 
  

    XFile xf; 
    URL url; 
    URLConnection urlConn; 
    InputStream  iStream; 
    OutputStream oStream; 
    long fp;    // file pointer 
  

    /** 
     * Open this file object 
     * 
     * @param xf the XFile for this file 
     * @param serial   true if serial access 
     * @param readOnly true if read only 
     */ 
    public boolean open(XFile xf, boolean serial, boolean readOnly) { 
  

        if (! serial) 
            return false; 
  

        this.xf = xf; 
  

        try { 
            url = new URL(xf.toString()); 
            urlConn = url.openConnection(); 
     
            urlConn.setDoInput(readOnly); 
            urlConn.setDoOutput(! readOnly); 
     
            urlConn.connect(); 
  

            return true; 
  

        } catch (IOException e) { 
            return false; 
        } 
    } 
  
  

    /** 
     * Get the XFile for this Accessor 
     * 
     * @return XFile for this object 
     */ 
    public XFile getXFile() { 
        return xf; 
    } 
  

    /** 
     * Tests if this XFileAccessor object exists.  
     * 
     * @return <code>true</code> if the file specified by this object 
     *         exists; <code>false</code> otherwise. 
     */ 
    public boolean exists() { 
        try { 
            if (iStream == null) 
                iStream = urlConn.getInputStream(); 
  

            return true; 
  

        } catch (IOException e) { 
            return false; 
        } 
    } 
  
  

    /** 
     * Tests if the application can write to this file.  
     * 
     * @return <code>true</code> if the application is allowed to 
     *         write to a file whose name is specified by this 
     *         object; <code>false</code> otherwise. 
     */ 
    public boolean canWrite() { 
        try { 
            if (oStream == null) 
                oStream = urlConn.getOutputStream(); 
  

             return true; 
  

        } catch (IOException e) { 
            return false; 
        } 
    } 
  
  

    /** 
     * Tests if the application can read from the specified file.  
     * 
     * @return <code>true</code> if the file specified by this 
     *         object exists and the application can read the file; 
     *         <code>false</code> otherwise. 
     */ 
    public boolean canRead() { 
        return exists(); 
    } 
  

    /** 
     * Tests if the file represented by this 
     * object is a "normal" file.  
     * <p> 
     * A file is "normal" if it is not a directory and, in  
     * addition, satisfies other system-dependent criteria. Any  
     * non-directory file created by a Java application is 
     * guaranteed to be a normal file.  
     * 
     * @return <code>true</code> if the file specified by this 
     *         <code>XFile</code> object exists and is a "normal" 
     *         file; <code>false</code> otherwise. 
     */ 
    public boolean isFile() { 
        return true; 
    } 
  
  

    /** 
     * Tests if the file represented by this XFileAccessor 
     * object is a directory.  
     * 
     * @return <code>true</code> if this XFileAccessor object 
     *         exists and is a directory; <code>false</code> 
     *         otherwise. 
     */ 
    public boolean isDirectory() { 
        return false; 
    } 
  
  

    /** 
     * Returns the time that the file represented by this  
     * <code>XFile</code> object was last modified.  
     * <p> 
     * The return value is system dependent and should only be 
     * used to compare with other values returned by last modified. 
     * It should not be interpreted as an absolute time.  
     * 
     * @return the time the file specified by this object was last 
     *         modified, or <code>0L</code> if the specified file 
     *         does not exist. 
     */ 
    public long lastModified() { 
        return urlConn.getLastModified(); 
    } 
  
  

    /** 
     * Returns the length of the file represented by this  
     * XFileAccessor object.  
     * 
     * @return the length, in bytes, of the file specified by 
     *         this object, or <code>0L</code> if the specified 
     *         file does not exist. 
     */ 
    public long length() { 
        long len = urlConn.getContentLength(); 
  

        return len < 0 ? 0 : len; 
    } 
  
  

    /** 
     * Creates a file whose pathname is specified by this  
     * XFileAccessor object.  
     * 
     * @return <code>true</code> if the file could be created; 
     *         <code>false</code> otherwise. 
     */ 
    public boolean mkfile() { 
        try { 
            if (oStream == null) 
                oStream = urlConn.getOutputStream(); 
  

            return true; 
  

        } catch (IOException e) { 
            return false; 
        } 
    } 
  
  

    /** 
     * Creates a directory whose pathname is specified by this  
     * XFileAccessor object.  
     * 
     * @return <code>true</code> if the directory could be created; 
     *         <code>false</code> otherwise. 
     */ 
    public boolean mkdir() { 
        return false; 
    } 
  
  

    /** 
     * Renames the file specified by this XFileAccessor object to  
     * have the pathname given by the XFileAccessor object argument.  
     * 
     * @param  dest   the new filename. 
     * @return <code>true</code> if the renaming succeeds; 
     *         <code>false</code> otherwise. 
     */ 
    public boolean renameTo(XFile dest) { 
        return false; 
    } 
  
  

    /** 
     * Returns a list of the files in the directory specified by 
     * this XFileAccessor object.  
     * 
     * @return an array of file names in the specified directory. 
     *         This list does not include the current directory or 
     *         the parent directory ("<code>.</code>" and 
     *         "<code>..</code>" on Unix systems). 
     */ 
    public String[] list() { 
        return new String[0]; 
    } 
  
  

    /** 
     * Deletes the file specified by this object.  If the target 
     * file to be deleted is a directory, it must be empty for 
     * deletion to succeed. 
     * 
     * @return <code>true</code> if the file is successfully deleted; 
     *         <code>false</code> otherwise. 
     */ 
    public boolean delete() { 
        return false; 
    } 
  
  

    /**  
     * Reads a subarray as a sequence of bytes.  
     * 
     * @param b the data to be written 
     * @param off the start offset in the data 
     * @param len the number of bytes that are written 
     * @param foff the offset into the file 
     * @return number of bytes read; -1 if EOF 
     * @exception IOException If an I/O error has occurred.  
     */  
    public int read(byte b[], int off, int len, long foff) 
        throws IOException { 
  

        int c; 
  

        if (iStream == null) 
            iStream = urlConn.getInputStream(); 
  

        if (foff > fp) { 
            iStream.skip(foff - fp); 
            fp = foff; 
        } 
  

        c = iStream.read(b, off, len); 
  

        if (c > 0) 
            fp += c; 
  

        return (c); 
    } 
  
  

    /** 
     * Writes a sub array as a sequence of bytes. 
     * 
     * @param b the data to be written 
     * @param off the start offset in the data 
     * @param len the number of bytes that are written 
     * @param foff the offset into the file 
     * @exception IOException If an I/O error has occurred. 
     */ 
    public void write(byte b[], int off, int len, long foff) 
        throws IOException { 
  

        if (oStream == null) 
            oStream = urlConn.getOutputStream(); 
  

        oStream.write(b, off, len); 
  

        fp += len; 
    } 
  
  

    /** 
     * Forces any buffered output bytes to be written out.  
     * <p> 
     * 
     * @exception  IOException  if an I/O error occurs. 
     */ 
    public void flush() throws IOException { 
        if (oStream != null) 
            oStream.flush(); 
    } 
  
  

    /** 
     * Close the Streams 
     * 
     * @exception IOException If an I/O error has occurred. 
     */ 
    public void close() throws IOException { 
  

        if (iStream != null) { 
            iStream.close(); 
            iStream = null; 
        } 
        if (oStream != null) { 
            oStream.close(); 
            oStream = null; 
        } 
    } 
     
  

    /** 
     * Returns a string representation of this object.  
     * 
     * @return a string giving the pathname of this object.  
     */ 
    public String toString() { 
        return url.toString(); 
    } 
} 

Listing 3.
 
import java.io.*; 
import com.sun.xfile.*; 
  

/* 
 * Recursive copy 
 * 
 * This program takes a file or directory hierarchy and 
 * copies it somewhere else. 
 * 
 * Notice that this program cares not whether the source 
 * and destination are local or remote. 
 * 
 * Usage: java rcopy <src> <dst> 
 * 
 */ 
class rcopy { 
  

    static void copy(XFile src, XFile dst) throws IOException { 
  

        if (src.isDirectory()) { 
  

            dst.mkdir(); 
  

            String[] dirList = src.list(); 
  

            for (int i = 0; i < dirList.length; i++) { 
                String entry = dirList[i]; 
  

                copy(new XFile(src, entry), new XFile(dst, entry)); 
            } 
  

        } else {        // assume it's a file 
  

            XFileInputStream  in  = new XFileInputStream(src); 
            XFileOutputStream out = new XFileOutputStream(dst); 
         
            int c; 
            byte[] buf = new byte[32768]; 
         
            while ((c = in.read(buf)) > 0) 
                out.write(buf, 0, c); 
         
            in.close(); 
            out.close(); 
        } 
    } 
  
  

    public static void main(String av[]) { 
        try { 
  

            copy(new XFile(av[0]), new XFile(av[1])); 
  

        } catch (IOException e) { 
            System.err.println(e); 
            e.printStackTrace(); 
        } 
    } 
} 
 
      
 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.