package railo.commons.io.res.type.s3; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Set; import org.xml.sax.SAXException; import railo.commons.io.res.Resource; import railo.commons.io.res.ResourceProvider; import railo.commons.io.res.util.ResourceSupport; import railo.commons.io.res.util.ResourceUtil; import railo.commons.lang.StringUtil; import railo.commons.net.http.httpclient3.HTTPEngine3Impl; import railo.loader.util.Util; import railo.runtime.exp.PageRuntimeException; import railo.runtime.op.Caster; import railo.runtime.type.Array; import railo.runtime.type.util.ListUtil; public final class S3Resource extends ResourceSupport { private static final long serialVersionUID = 2265457088552587701L; private static final long FUTURE=50000000000000L; private static final S3Info UNDEFINED=new Dummy("undefined",0,0,false,false,false); private static final S3Info ROOT=new Dummy("root",0,0,true,false,true); private static final S3Info LOCKED = new Dummy("locked",0,0,true,false,false); private static final S3Info UNDEFINED_WITH_CHILDREN = new Dummy("undefined with children 1",0,0,true,false,true); private static final S3Info UNDEFINED_WITH_CHILDREN2 = new Dummy("undefined with children 2",0,0,true,false,true); private final S3ResourceProvider provider; private final String bucketName; private String objectName; private final S3 s3; long infoLastAccess=0; private int storage=S3.STORAGE_UNKNOW; private int acl=S3.ACL_PUBLIC_READ; private boolean newPattern; private S3Resource(S3 s3,int storage, S3ResourceProvider provider, String buckedName,String objectName, boolean newPattern) { this.s3=s3; this.provider=provider; this.bucketName=buckedName; this.objectName=objectName; this.storage=storage; this.newPattern=newPattern; } S3Resource(S3 s3,int storage, S3ResourceProvider provider, String path, boolean newPattern) { this.s3=s3; this.provider=provider; this.newPattern=newPattern; if(path.equals("/") || Util.isEmpty(path,true)) { this.bucketName=null; this.objectName=""; } else { path=ResourceUtil.translatePath(path, true, false); String[] arr = toStringArray( ListUtil.listToArrayRemoveEmpty(path,"/")); bucketName=arr[0]; for(int i=1;i<arr.length;i++) { if(Util.isEmpty(objectName))objectName=arr[i]; else objectName+="/"+arr[i]; } if(objectName==null)objectName=""; } this.storage=storage; } public static String[] toStringArray(Array array) { String[] arr=new String[array.size()]; for(int i=0;i<arr.length;i++) { arr[i]=Caster.toString(array.get(i+1,""),""); } return arr; } public void createDirectory(boolean createParentWhenNotExists) throws IOException { ResourceUtil.checkCreateDirectoryOK(this, createParentWhenNotExists); try { provider.lock(this); if(isBucket()) { s3.putBuckets(bucketName, acl,storage); } else s3.put(bucketName, objectName+"/", acl, HTTPEngine3Impl.getEmptyEntity("application")); } catch (IOException ioe) { throw ioe; } catch (Exception e) { e.printStackTrace(); throw new IOException(e.getMessage()); } finally { provider.unlock(this); } s3.releaseCache(getInnerPath()); } public void createFile(boolean createParentWhenNotExists) throws IOException { ResourceUtil.checkCreateFileOK(this, createParentWhenNotExists); if(isBucket()) throw new IOException("can't create file ["+getPath()+"], on this level (Bucket Level) you can only create directories"); try { provider.lock(this); s3.put(bucketName, objectName, acl, HTTPEngine3Impl.getEmptyEntity("application")); } catch (Exception e) { throw new IOException(e.getMessage()); } finally { provider.unlock(this); } s3.releaseCache(getInnerPath()); } public boolean exists() { return getInfo() .exists(); } public InputStream getInputStream() throws IOException { ResourceUtil.checkGetInputStreamOK(this); provider.read(this); try { return Util.toBufferedInputStream(s3.getInputStream(bucketName, objectName)); } catch (Exception e) { throw new IOException(e.getMessage()); } } public int getMode() { return 777; } @Override public String getName() { if(isRoot()) return ""; if(isBucket()) return bucketName; return objectName.substring(objectName.lastIndexOf('/')+1); } @Override public boolean isAbsolute() { return true; } @Override public String getPath() { return getPrefix().concat(getInnerPath()); } private String getPrefix() { String aki=s3.getAccessKeyId(); String sak=s3.getSecretAccessKey(); StringBuilder sb=new StringBuilder(provider.getScheme()).append("://"); if(!StringUtil.isEmpty(aki)){ sb.append(aki); if(!StringUtil.isEmpty(sak)){ sb.append(":").append(sak); if(storage!=S3.STORAGE_UNKNOW){ sb.append(":").append(S3.toStringStorage(storage,"us")); } } sb.append("@"); } if(!newPattern) sb.append(s3.getHost()); return sb.toString(); } @Override public String getParent() { if(isRoot()) return null; return getPrefix().concat(getInnerParent()); } private String getInnerPath() { if(isRoot()) return "/"; return ResourceUtil.translatePath(bucketName+"/"+objectName, true, false); } private String getInnerParent() { if(isRoot()) return null; if(Util.isEmpty(objectName)) return "/"; if(objectName.indexOf('/')==-1) return "/"+bucketName; String tmp=objectName.substring(0,objectName.lastIndexOf('/')); return ResourceUtil.translatePath(bucketName+"/"+tmp, true, false); } @Override public Resource getParentResource() { if(isRoot()) return null; return new S3Resource(s3,isBucket()?S3.STORAGE_UNKNOW:storage,provider,getInnerParent(),newPattern);// MUST direkter machen } private boolean isRoot() { return bucketName==null; } private boolean isBucket() { return bucketName!=null && Util.isEmpty(objectName); } @Override public String toString() { return getPath(); } public OutputStream getOutputStream(boolean append) throws IOException { ResourceUtil.checkGetOutputStreamOK(this); //provider.lock(this); try { byte[] barr = null; if(append){ InputStream is=null; OutputStream os=null; try{ ByteArrayOutputStream baos = new ByteArrayOutputStream(); os=baos; Util.copy(is=getInputStream(), baos); barr=baos.toByteArray(); } catch (Exception e) { throw new PageRuntimeException(Caster.toPageException(e)); } finally{ Util.closeEL(is); Util.closeEL(os); } } S3ResourceOutputStream os = new S3ResourceOutputStream(s3,bucketName,objectName,acl); if(append && !(barr==null || barr.length==0)) Util.copy(new ByteArrayInputStream(barr),os); return os; } catch(IOException e) { throw e; } catch (Exception e) { throw new PageRuntimeException(Caster.toPageException(e)); } finally { s3.releaseCache(getInnerPath()); } } @Override public Resource getRealResource(String realpath) { realpath=ResourceUtil.merge(getInnerPath(), realpath); if(realpath.startsWith("../"))return null; return new S3Resource(s3,S3.STORAGE_UNKNOW,provider,realpath,newPattern); } @Override public ResourceProvider getResourceProvider() { return provider; } @Override public boolean isDirectory() { return getInfo().isDirectory(); } @Override public boolean isFile() { return getInfo().isFile(); } public boolean isReadable() { return exists(); } public boolean isWriteable() { return exists(); } @Override public long lastModified() { return getInfo().getLastModified(); } private S3Info getInfo() { S3Info info = s3.getInfo(getInnerPath()); if(info==null) {// || System.currentTimeMillis()>infoLastAccess if(isRoot()) { try { s3.listBuckets(); info=ROOT; } catch (Exception e) { info=UNDEFINED; } infoLastAccess=FUTURE; } else { try { provider.read(this); } catch (IOException e) { return LOCKED; } try { if(isBucket()) { Bucket[] buckets = s3.listBuckets(); String name=getName(); for(int i=0;i<buckets.length;i++) { if(buckets[i].getName().equals(name)) { info=buckets[i]; infoLastAccess=System.currentTimeMillis()+provider.getCache(); break; } } } else { try { // first check if the bucket exists // TODO not happy about this step Bucket[] buckets = s3.listBuckets(); boolean bucketExists=false; for(int i=0;i<buckets.length;i++) { if(buckets[i].getName().equals(bucketName)) { bucketExists=true; break; } } if(bucketExists){ String path = objectName; Content[] contents = s3.listContents(bucketName, path); if(contents.length>0) { boolean has=false; for(int i=0;i<contents.length;i++) { if(ResourceUtil.translatePath(contents[i].getKey(),false,false).equals(path)) { has=true; info=contents[i]; infoLastAccess=System.currentTimeMillis()+provider.getCache(); break; } } if(!has){ for(int i=0;i<contents.length;i++) { if(ResourceUtil.translatePath(contents[i].getKey(),false,false).startsWith(path)) { info=UNDEFINED_WITH_CHILDREN; infoLastAccess=System.currentTimeMillis()+provider.getCache(); break; } } } } } } catch(SAXException e) { } } if(info==null){ info=UNDEFINED; infoLastAccess=System.currentTimeMillis()+provider.getCache(); } } catch(Exception t) { return UNDEFINED; } } s3.setInfo(getInnerPath(), info); } return info; } @Override public long length() { return getInfo().getSize(); } public Resource[] listResources() { S3Resource[] children=null; try { if(isRoot()) { Bucket[] buckets = s3.listBuckets(); children=new S3Resource[buckets.length]; for(int i=0;i<children.length;i++) { children[i]=new S3Resource(s3,storage,provider,buckets[i].getName(),"",newPattern); s3.setInfo(children[i].getInnerPath(),buckets[i]); } } else if(isDirectory()){ Content[] contents = s3.listContents(bucketName, isBucket()?null:objectName+"/"); ArrayList<S3Resource> tmp = new ArrayList<S3Resource>(); String key,name,path; int index; Set<String> names=new LinkedHashSet<String>(); Set<String> pathes=new LinkedHashSet<String>(); S3Resource r; boolean isb=isBucket(); for(int i=0;i<contents.length;i++) { key=ResourceUtil.translatePath(contents[i].getKey(), false, false); if(!isb && !key.startsWith(objectName+"/")) continue; if(Util.isEmpty(key)) continue; index=key.indexOf('/',Util.length(objectName)+1); if(index==-1) { name=key; path=null; } else { name=key.substring(index+1); path=key.substring(0,index); } //print.out("1:"+key); //print.out("path:"+path); //print.out("name:"+name); if(path==null){ names.add(name); tmp.add(r=new S3Resource(s3,storage,provider,contents[i].getBucketName(),key,newPattern)); s3.setInfo(r.getInnerPath(),contents[i]); } else { pathes.add(path); } } Iterator<String> it = pathes.iterator(); while(it.hasNext()) { path=it.next(); if(names.contains(path)) continue; tmp.add(r=new S3Resource(s3,storage,provider,bucketName,path,newPattern)); s3.setInfo(r.getInnerPath(),UNDEFINED_WITH_CHILDREN2); } //if(tmp.size()==0 && !isDirectory()) return null; children=tmp.toArray(new S3Resource[tmp.size()]); } } catch(Exception t) { t.printStackTrace(); return null; } return children; } @Override public void remove(boolean force) throws IOException { if(isRoot()) throw new IOException("can not remove root of S3 Service"); ResourceUtil.checkRemoveOK(this); boolean isd=isDirectory(); if(isd) { Resource[] children = listResources(); if(children.length>0) { if(force) { for(int i=0;i<children.length;i++) { children[i].remove(force); } } else { throw new IOException("can not remove directory ["+this+"], directory is not empty"); } } } // delete res itself provider.lock(this); try { s3.delete(bucketName, isd?objectName+"/":objectName); } catch (Exception e) { throw new IOException(e.getMessage()); } finally { s3.releaseCache(getInnerPath()); provider.unlock(this); } } public boolean setLastModified(long time) { s3.releaseCache(getInnerPath()); // TODO Auto-generated method stub return false; } public void setMode(int mode) throws IOException { s3.releaseCache(getInnerPath()); // TODO Auto-generated method stub } public boolean setReadable(boolean readable) { s3.releaseCache(getInnerPath()); // TODO Auto-generated method stub return false; } public boolean setWritable(boolean writable) { s3.releaseCache(getInnerPath()); // TODO Auto-generated method stub return false; } public AccessControlPolicy getAccessControlPolicy() { String p = getInnerPath(); try { AccessControlPolicy acp = s3.getACP(p); if(acp==null){ acp=s3.getAccessControlPolicy(bucketName, getObjectName()); s3.setACP(p, acp); } return acp; } catch (Exception e) { throw new PageRuntimeException(Caster.toPageException(e)); } } public void setAccessControlPolicy(AccessControlPolicy acp) { try { s3.setAccessControlPolicy(bucketName, getObjectName(),acp); } catch (Exception e) { throw new PageRuntimeException(Caster.toPageException(e)); } finally { s3.releaseCache(getInnerPath()); } } private String getObjectName() { if(!StringUtil.isEmpty(objectName) && isDirectory()) { return objectName+"/"; } return objectName; } public void setACL(int acl) { this.acl=acl; } public void setStorage(int storage) { this.storage=storage; } } class Dummy implements S3Info { private long lastModified; private long size; private boolean exists; private boolean file; private boolean directory; private String label; public Dummy(String label,long lastModified, long size, boolean exists,boolean file, boolean directory) { this.label = label; this.lastModified = lastModified; this.size = size; this.exists = exists; this.file = file; this.directory = directory; } @Override public long getLastModified() { return lastModified; } @Override public long getSize() { return size; } @Override public String toString() { return "Dummy:"+getLabel(); } /** * @return the label */ public String getLabel() { return label; } @Override public boolean exists() { return exists; } @Override public boolean isDirectory() { return directory; } @Override public boolean isFile() { return file; } }