package com.intridea.io.vfs.provider.s3; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import org.apache.commons.vfs.FileName; import org.apache.commons.vfs.FileObject; import org.apache.commons.vfs.FileSystemException; import org.apache.commons.vfs.FileType; import org.apache.commons.vfs.provider.AbstractFileObject; import org.apache.commons.vfs.util.MonitorOutputStream; import org.aw20.amazon.S3; import org.aw20.amazon.S3Exception; import org.aw20.amazon.S3Result; import com.naryx.tagfusion.cfm.file.MimeLookup; /** * Implementation of the virtual S3 file system object using the Jets3t library. * Based on Matthias Jugel code * http://thinkberg.com/svn/moxo/trunk/src/main/java/com/thinkberg/moxo/ * * @author Marat Komarov * @author Matthias L. Jugel * * Modified by Alan Williamson @ September 2009 * */ public class S3FileObject extends AbstractFileObject { /** * Amazon S3 service */ private final S3 service; private final String bucket; private S3Result s3result; /** * True when content attached to file */ private boolean attached = false; /** * True when content downloaded. It's an extended flag to * <code>attached</code>. */ private boolean downloaded = false; /** * Local cache of file content */ private File cacheFile; /** * Class logger */ private FileName fileName; private S3FileSystem fileSystem; public S3FileObject(FileName fileName, S3FileSystem fileSystem, S3 service, String bucket) throws FileSystemException { super(fileName, fileSystem); this.service = service; this.bucket = bucket; this.fileName = fileName; this.fileSystem = fileSystem; } public FileObject resolveFile(String f) throws FileSystemException { if ( f != null ) return super.resolveFile(f); else{ // Special constructor to create a new version return new S3FileObject( fileName, fileSystem, service, bucket ); } } public void close(){ if ( cacheFile != null ){ cacheFile.delete(); cacheFile = null; } } protected void doAttach() throws Exception { if (!attached) { try { s3result = service.getInfo(bucket, getS3Key() ); } catch (S3Exception e) { s3result = null; downloaded = true; } attached = true; } } protected void doDetach() throws Exception { if (attached) { downloaded = false; attached = false; } if ( cacheFile != null ){ cacheFile.delete(); cacheFile = null; } } protected void doDelete() throws Exception { service.delete(bucket, getS3Key() ); } protected void doRename(FileObject newfile) throws Exception { throw new Exception( "doRename not supported" ); } protected void doCreateFolder() throws Exception { return; } protected long doGetLastModifiedTime() throws Exception { if ( s3result != null ){ return s3result.getLastModified(); }else return 0; } protected void doSetLastModifiedTime(final long modtime) throws Exception { System.out.println( "S3:doSetLastModifiedTime" ); } protected InputStream doGetInputStream() throws Exception { downloadOnce(); if ( downloaded ){ return new java.io.FileInputStream( cacheFile ); }else throw new Exception( "file not available" ); } protected OutputStream doGetOutputStream(boolean bAppend) throws Exception { if ( bAppend ) throw new Exception("Append Operation not supported"); if ( cacheFile != null ){ cacheFile.delete(); } cacheFile = File.createTempFile( "openbd.", ".out.s3" ); return new S3OutputStream( new FileOutputStream( cacheFile ), service, getS3Key() ); } protected FileType doGetType() throws Exception { if ("".equals(this.getS3Key()) || this.getS3Key().endsWith("/") ) { return FileType.FOLDER; }else{ return FileType.FILE; } } protected String[] doListChildren() throws Exception { String path = getS3Key(); // make sure we add a '/' slash at the end to find children if (!"".equals(path)) { path = path + "/"; } S3Result listResult = service.bucketListAll( bucket, path ); String[] childrenNames = new String[ listResult.fileList.size() ]; for (int i = 0; i < childrenNames.length; i++) { childrenNames[i] = listResult.fileList.get(i).key.replaceAll("[^/]*//*", ""); } return childrenNames; } protected long doGetContentSize() throws Exception { if ( s3result != null ){ String contentLength = s3result.getHttpHeader("content-length"); if ( contentLength != null ) return Long.valueOf(contentLength); } return 0; } // Utility methods /** * Download S3 object content and save it in temporary file. Do it only if * object was not already downloaded. */ private void downloadOnce() throws FileSystemException { if (!downloaded) { if ( cacheFile != null ){ cacheFile.delete(); } final String failedMessage = "Failed to download S3 Object %s. %s"; final String objectPath = getName().getPath(); BufferedInputStream reader = null; BufferedOutputStream writer = null; FileOutputStream fileStream = null; try { cacheFile = File.createTempFile( "openbd.", ".in.s3" ); fileStream = new FileOutputStream(cacheFile); s3result = service.get(bucket, getS3Key(), null, S3.READ_TIMEOUT, fileStream ); } catch (S3Exception e) { throw new FileSystemException(String.format(failedMessage, objectPath, e.getMessage()), e); } catch (IOException e) { throw new FileSystemException(String.format(failedMessage, objectPath, e.getMessage()), e); } finally { if ( reader != null ){ try {reader.close();} catch (IOException e) {} } if ( writer != null ){ try {writer.flush(); writer.close();} catch (IOException e) {} } if ( fileStream != null ){ try {fileStream.close();} catch (IOException e) {} } } downloaded = true; } } /** * Create an S3 key from a commons-vfs path. This simply strips the slash from * the beginning if it exists. * * @return the S3 object key */ private String getS3Key() { String path = getName().getPath(); if ("".equals(path)) { return path; } else { return path.substring(1); } } /** * Special JetS3FileObject output stream. It saves all contents in temporary * file, onClose sends contents to S3. */ private class S3OutputStream extends MonitorOutputStream { private S3 service; private String object; public S3OutputStream(OutputStream out, S3 service, String object) { super(out); this.service = service; this.object = object; } protected void onClose() throws IOException { out.flush(); out.close(); try { service.put( bucket, object, MimeLookup.getMimeType(object), cacheFile ); } catch (S3Exception e) { throw new IOException(e.getMessage()); }finally{ cacheFile.delete(); cacheFile = null; } } } }