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;
}
}