Monday, June 1, 2009

Add plugin for Apache VFS

My customer wants to upload files by https based on our current upload framework which depends on Apache VFS. Apache VFS only supports "READ" on HTTP/HTTPS. I generats the plugin to extend VFS to support "WRITE" on HTTP/HTTPS.

The idea of Apache VFS is using the same API for accessing various different file systems, such like FTP, SFTP, HTTP, ZIP...and so on.

To make HTTP/HTTPS writable, I added 3 classes.

  • WritableHttpFileObject
  • WritableHttpProvider
  • WritableHttpFileSystem

 

WritableHttpFileObject
import java.io.FileOutputStream;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.multipart.ByteArrayPartSource;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.vfs.FileName;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSelector;
import org.apache.commons.vfs.FileSystemException;
import org.apache.commons.vfs.FileType;
import org.apache.commons.vfs.Selectors;
import org.apache.commons.vfs.impl.StandardFileSystemManager;
import org.apache.commons.vfs.provider.http.HttpFileObject;
import org.apache.commons.vfs.provider.http.HttpFileSystem;



/**
* A writable file on an Http/Https service.
*
* The only really useful method is copyFrom() which causes the source file
* to actually be POSTed to the server with the specified filename.
*/
public class WritableHttpFileObject extends HttpFileObject {
private static final Log LOG = LogFactory.getLog(WritableHttpFileObject.class);

private FileOutputStream outputStream;
private String targetURL;
private final WritableHttpFileSystem fileSystem;

public WritableHttpFileObject(FileName fileName, WritableHttpFileSystem fileSystem) {
super(fileName, fileSystem);
this.fileSystem = fileSystem;
}


public boolean isWriteable() throws FileSystemException {
return true;
}

public void createFile() throws FileSystemException {
// Nothing to do
}

public void copyFrom(FileObject fileObject, FileSelector selector)
throws FileSystemException {

if (!(fileObject.getType().equals(FileType.FILE) && selector.equals(Selectors.SELECT_SELF))) {
throw new FileSystemException("Can only copy one files (one at a time)");
}

PostMethod filePost = new PostMethod(getURL().toString());


StandardFileSystemManager fsManager = new StandardFileSystemManager();
try {
LOG.info("Uploading " + fileObject.getName() + " to " + targetURL);

byte[] fileContent = IOUtils.toByteArray(fileObject.getContent().getInputStream());
ByteArrayPartSource byteArrayPartSource = new ByteArrayPartSource(fileObject.getName().getBaseName(), fileContent);
Part[] parts = {
new FilePart(fileObject.getName().getBaseName(), byteArrayPartSource)
};
filePost.setRequestEntity(
new MultipartRequestEntity(parts, filePost.getParams())
);

HttpClient client = fileSystem.getClient();

client.getHttpConnectionManager().
getParams().setConnectionTimeout(5000);
int status = client.executeMethod(filePost);
if (status == HttpStatus.SC_OK) {
appendMessage(
"Upload complete, response=" + filePost.getResponseBodyAsString()
);
} else {
appendMessage(
"Upload failed, response=" + HttpStatus.getStatusText(status)
);
}
} catch (Exception ex) {
appendMessage("ERROR: " + ex.getClass().getName() + " " + ex.getMessage());
ex.printStackTrace();
} finally {
filePost.releaseConnection();
}
}

private void appendMessage(final String msg) {
LOG.info(msg);
}

}

WritableHttpFileProvider

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.vfs.Capability;
import org.apache.commons.vfs.FileName;
import org.apache.commons.vfs.FileSystem;
import org.apache.commons.vfs.FileSystemException;
import org.apache.commons.vfs.FileSystemOptions;
import org.apache.commons.vfs.UserAuthenticationData;
import org.apache.commons.vfs.provider.GenericFileName;
import org.apache.commons.vfs.provider.http.HttpClientFactory;
import org.apache.commons.vfs.provider.http.HttpFileProvider;
import org.apache.commons.vfs.provider.http.HttpFileSystem;
import org.apache.commons.vfs.util.UserAuthenticatorUtils;

public class WritableHttpFileProvider extends HttpFileProvider {

final static Collection capabilities = Collections.unmodifiableCollection(Arrays.asList(new Capability[]
{
Capability.GET_TYPE,
Capability.READ_CONTENT,
Capability.WRITE_CONTENT,
Capability.URI,
Capability.GET_LAST_MODIFIED,
Capability.ATTRIBUTES,
Capability.RANDOM_ACCESS_READ
}));

/**
* Creates a {@link FileSystem}.
*/
protected FileSystem doCreateFileSystem(final FileName name, final FileSystemOptions fileSystemOptions)
throws FileSystemException
{
// Create the file system
final GenericFileName rootName = (GenericFileName) name;

UserAuthenticationData authData = null;
HttpClient httpClient;
try
{
authData = UserAuthenticatorUtils.authenticate(fileSystemOptions, AUTHENTICATOR_TYPES);

httpClient = HttpClientFactory.createConnection(
rootName.getScheme(),
rootName.getHostName(),
rootName.getPort(),
UserAuthenticatorUtils.toString(UserAuthenticatorUtils.getData(authData, UserAuthenticationData.USERNAME, UserAuthenticatorUtils.toChar(rootName.getUserName()))),
UserAuthenticatorUtils.toString(UserAuthenticatorUtils.getData(authData, UserAuthenticationData.PASSWORD, UserAuthenticatorUtils.toChar(rootName.getPassword()))),
fileSystemOptions);
}
finally
{
UserAuthenticatorUtils.cleanup(authData);
}

return new WritableHttpFileSystem(rootName, httpClient, fileSystemOptions);
}
}

WritableHttpFileSystem

import java.util.Collection;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.vfs.FileName;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemOptions;
import org.apache.commons.vfs.provider.GenericFileName;
import org.apache.commons.vfs.provider.http.HttpFileSystem;

public class WritableHttpFileSystem extends HttpFileSystem {

protected WritableHttpFileSystem(GenericFileName rootName,
HttpClient client, FileSystemOptions fileSystemOptions) {
super(rootName, client, fileSystemOptions);
}

protected void addCapabilities(Collection caps)
{
caps.addAll(WritableHttpFileProvider.capabilities);
}

protected FileObject createFile(FileName name)
throws Exception
{
return new WritableHttpFileObject(name, this);
}

protected HttpClient getClient()
{
return super.getClient();
}

}

On the server side, you should have a servlet or controller to get the file from the request and save it as a file. To test it quickly, I create a jsp

<%@ page contentType="text/html;charset=utf-8"%>
<%@ page import="org.apache.commons.fileupload.DiskFileUpload"%>
<%@ page import="org.apache.commons.fileupload.FileItem"%>
<%@ page import="java.util.List"%>
<%@ page import="java.util.Iterator"%>
<%@ page import="java.io.File"%>
<%
System.out.println("Content Type ="+request.getContentType());

if (ServletFileUpload.isMultipartContent(request)){
// Parse the HTTP request...
ServletFileUpload servletFileUpload = new ServletFileUpload(new DiskFileItemFactory());
List fileItemsList = servletFileUpload.parseRequest(request);
Iterator it = fileItemsList.iterator();
while (it.hasNext()){
FileItem fileItem = (FileItem)it.next();
if (fileItem.isFormField()){
System.out.println("Field ="+fileItem.getFieldName());
}
else{
System.out.println("\nNAME: "+fileItem.getName());
System.out.println("SIZE: "+fileItem.getSize());
//System.out.println(fi.getOutputStream().toString());
File fNew= new File(request.getPathInfo(), fileItem.getName());

System.out.println("\n"+fNew.getAbsolutePath());
fileItem.write(fNew);
}
}
} else{
System.out.println("the servlet request is not multipart content");
}
%>


The class to test WritableHttpFileObject



import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemException;
import org.apache.commons.vfs.FileSystemOptions;
import org.apache.commons.vfs.Selectors;
import org.apache.commons.vfs.cache.DefaultFilesCache;
import org.apache.commons.vfs.impl.DefaultFileSystemManager;
import org.apache.commons.vfs.impl.StandardFileSystemManager;
import org.apache.commons.vfs.provider.http.HttpFileProvider;
import org.apache.commons.vfs.provider.http.HttpFileSystemConfigBuilder;
import org.apache.commons.vfs.provider.local.DefaultLocalFileProvider;
import org.junit.Test;

public class WritableHttpFileObjectTest {

@Test
public void testCopyFromFileObjectFileSelector() {
String filePath = "C:/temp/test.txt";
String uri = "http://localhost:8080/cmserver/test.jsp";

System.out.println("File Path: " + filePath);

DefaultFileSystemManager manager = getDefaultFileSystemManager();
try {
FileObject localFileObject = manager.resolveFile(filePath);

FileObject fileObject = manager.resolveFile(uri, null);

fileObject.copyFrom(localFileObject, Selectors.SELECT_SELF);
} catch (Exception e) {
e.printStackTrace();
}
finally {
manager.close();
}
}

private DefaultFileSystemManager getDefaultFileSystemManager() {
DefaultFileSystemManager mgr = new DefaultFileSystemManager();

WritableHttpFileProvider writableHttpFileProvider = new WritableHttpFileProvider();
DefaultLocalFileProvider localFileProvider = new DefaultLocalFileProvider();

try {
mgr.addProvider("http", writableHttpFileProvider);
mgr.addProvider("file", localFileProvider);
mgr.setFilesCache(new DefaultFilesCache());
mgr.init();
} catch (FileSystemException e) {
e.printStackTrace();
}

return mgr;
}

}


The simplest method is to use the static VFS.getManager() method, which returns the default Commons VFS implementation.



This method will also automatically scan the classpath for a /META-INF/vfs-providers.xml file (also in jar files). If such a file is found Commons VFS uses it in addition to the default providers.xml (it is in org.apache.commons.vfs.iml in the vfs jar). This allows you to start using a new filesystem by simply drop its implementation into the classpath. The configuration file format is described below.


Notice: Currently it is not allowed to override a already configured filesystem. Commons VFS throws an exception if there is already a filesystem for a scheme.

No comments:

Post a Comment