001    package railo.commons.io.res.type.s3;
002    
003    import java.io.ByteArrayInputStream;
004    import java.io.ByteArrayOutputStream;
005    import java.io.IOException;
006    import java.io.InputStream;
007    import java.io.OutputStream;
008    import java.util.ArrayList;
009    import java.util.Iterator;
010    import java.util.LinkedHashSet;
011    import java.util.Set;
012    
013    import org.xml.sax.SAXException;
014    
015    import railo.commons.io.res.Resource;
016    import railo.commons.io.res.ResourceProvider;
017    import railo.commons.io.res.util.ResourceSupport;
018    import railo.commons.io.res.util.ResourceUtil;
019    import railo.commons.lang.StringUtil;
020    import railo.commons.net.http.httpclient3.HTTPEngine3Impl;
021    import railo.loader.util.Util;
022    import railo.runtime.exp.PageRuntimeException;
023    import railo.runtime.op.Caster;
024    import railo.runtime.type.Array;
025    import railo.runtime.type.util.ListUtil;
026    
027    public final class S3Resource extends ResourceSupport {
028    
029            private static final long serialVersionUID = 2265457088552587701L;
030    
031            private static final long FUTURE=50000000000000L;
032            
033            private static final S3Info UNDEFINED=new Dummy("undefined",0,0,false,false,false);
034            private static final S3Info ROOT=new Dummy("root",0,0,true,false,true);
035            private static final S3Info LOCKED = new Dummy("locked",0,0,true,false,false);
036            private static final S3Info UNDEFINED_WITH_CHILDREN = new Dummy("undefined with children 1",0,0,true,false,true);
037            private static final S3Info UNDEFINED_WITH_CHILDREN2 = new Dummy("undefined with children 2",0,0,true,false,true);
038    
039    
040            private final S3ResourceProvider provider;
041            private final String bucketName;
042            private String objectName;
043            private final S3 s3;
044            long infoLastAccess=0;
045            private int storage=S3.STORAGE_UNKNOW;
046            private int acl=S3.ACL_PUBLIC_READ;
047    
048            private boolean newPattern;
049    
050            private S3Resource(S3 s3,int storage, S3ResourceProvider provider, String buckedName,String objectName, boolean newPattern) {
051                    this.s3=s3;
052                    this.provider=provider;
053                    this.bucketName=buckedName;
054                    this.objectName=objectName;
055                    this.storage=storage;
056                    this.newPattern=newPattern;
057            }
058            
059    
060            S3Resource(S3 s3,int storage, S3ResourceProvider provider, String path, boolean newPattern) {
061                    this.s3=s3;
062                    this.provider=provider;
063                    this.newPattern=newPattern;
064                    
065                    if(path.equals("/") || Util.isEmpty(path,true)) {
066                            this.bucketName=null;
067                            this.objectName="";
068                    }
069                    else {
070                            path=ResourceUtil.translatePath(path, true, false);
071                            String[] arr = toStringArray( ListUtil.listToArrayRemoveEmpty(path,"/"));
072                            bucketName=arr[0];
073                            for(int i=1;i<arr.length;i++) {
074                                    if(Util.isEmpty(objectName))objectName=arr[i];
075                                    else objectName+="/"+arr[i];
076                            }
077                            if(objectName==null)objectName="";
078                    }
079                    this.storage=storage;
080                    
081            }
082    
083            public  static String[] toStringArray(Array array) {
084            String[] arr=new String[array.size()];
085            for(int i=0;i<arr.length;i++) {
086                arr[i]=Caster.toString(array.get(i+1,""),"");
087            }
088            return arr;
089        }
090    
091    
092            public void createDirectory(boolean createParentWhenNotExists) throws IOException {
093                    ResourceUtil.checkCreateDirectoryOK(this, createParentWhenNotExists);
094                    try {
095                            provider.lock(this);
096                            if(isBucket()) {
097                                    s3.putBuckets(bucketName, acl,storage);
098                            }
099                            else s3.put(bucketName, objectName+"/", acl, HTTPEngine3Impl.getEmptyEntity("application"));    
100                    }
101                    catch (IOException ioe) {
102                            throw ioe;
103                    }
104                    catch (Exception e) {
105                            e.printStackTrace();
106                            throw new IOException(e.getMessage());
107                    }
108                    finally {
109                            provider.unlock(this);
110                    }
111                    s3.releaseCache(getInnerPath());
112            }
113    
114            public void createFile(boolean createParentWhenNotExists) throws IOException {
115                    ResourceUtil.checkCreateFileOK(this, createParentWhenNotExists);
116                    if(isBucket()) throw new IOException("can't create file ["+getPath()+"], on this level (Bucket Level) you can only create directories");
117                    try {
118                            provider.lock(this);
119                            s3.put(bucketName, objectName, acl, HTTPEngine3Impl.getEmptyEntity("application"));
120                    } 
121                    catch (Exception e) {
122                            throw new IOException(e.getMessage());
123                    }
124                    finally {
125                            provider.unlock(this);
126                    }
127                    s3.releaseCache(getInnerPath());
128            }
129    
130            public boolean exists() {
131                    
132                    return getInfo()
133                            .exists();
134            }
135    
136            public InputStream getInputStream() throws IOException {
137                    ResourceUtil.checkGetInputStreamOK(this);
138                    provider.read(this);
139                    try {
140                            return Util.toBufferedInputStream(s3.getInputStream(bucketName, objectName));
141                    } 
142                    catch (Exception e) {
143                            throw new IOException(e.getMessage());
144                    }
145            }
146    
147            public int getMode() {
148                    return 777;
149            }
150    
151            @Override
152            public String getName() {
153                    if(isRoot()) return "";
154                    if(isBucket()) return bucketName;
155                    return objectName.substring(objectName.lastIndexOf('/')+1);
156            }
157    
158            @Override
159            public boolean isAbsolute() {
160                    return true;
161            }
162            
163    
164            @Override
165            public String getPath() {
166                    return getPrefix().concat(getInnerPath());
167            }
168            
169            private String getPrefix()  {
170                    
171                    String aki=s3.getAccessKeyId();
172                    String sak=s3.getSecretAccessKey();
173                    
174                    StringBuilder sb=new StringBuilder(provider.getScheme()).append("://");
175                    
176                    if(!StringUtil.isEmpty(aki)){
177                            sb.append(aki);
178                            if(!StringUtil.isEmpty(sak)){
179                                    sb.append(":").append(sak);
180                                    if(storage!=S3.STORAGE_UNKNOW){
181                                            sb.append(":").append(S3.toStringStorage(storage,"us"));
182                                    }
183                            }
184                            sb.append("@");
185                    }
186                    if(!newPattern)
187                            sb.append(s3.getHost());
188                    
189                    return sb.toString();
190            }
191    
192    
193            @Override
194            public String getParent() {
195                    if(isRoot()) return null;
196                    return getPrefix().concat(getInnerParent());
197            }
198            
199            private String getInnerPath() {
200                    if(isRoot()) return "/";
201                    return ResourceUtil.translatePath(bucketName+"/"+objectName, true, false);
202            }
203            
204            private String getInnerParent() {
205                    if(isRoot()) return null;
206                    if(Util.isEmpty(objectName)) return "/";
207                    if(objectName.indexOf('/')==-1) return "/"+bucketName;
208                    String tmp=objectName.substring(0,objectName.lastIndexOf('/'));
209                    return ResourceUtil.translatePath(bucketName+"/"+tmp, true, false);
210            }
211    
212            @Override
213            public Resource getParentResource() {
214                    if(isRoot()) return null;
215                    return new S3Resource(s3,isBucket()?S3.STORAGE_UNKNOW:storage,provider,getInnerParent(),newPattern);// MUST direkter machen
216            }
217    
218            private boolean isRoot() {
219                    return bucketName==null;
220            }
221            
222            private boolean isBucket() {
223                    return bucketName!=null && Util.isEmpty(objectName);
224            }
225    
226            @Override
227            public String toString() {
228                    return getPath();
229            }
230            
231            public OutputStream getOutputStream(boolean append) throws IOException {
232    
233                    ResourceUtil.checkGetOutputStreamOK(this);
234                    //provider.lock(this);
235                    
236                    try {
237                            byte[] barr = null;
238                            if(append){
239                                    InputStream is=null;
240                                    OutputStream os=null;
241                                    try{
242                                            ByteArrayOutputStream baos = new ByteArrayOutputStream();
243                                            os=baos;
244                                            Util.copy(is=getInputStream(), baos);
245                                            barr=baos.toByteArray();
246                                    }
247                                    catch (Exception e) {
248                                            throw new PageRuntimeException(Caster.toPageException(e));
249                                    }
250                                    finally{
251                                            Util.closeEL(is);
252                                            Util.closeEL(os);
253                                    }
254                            }
255                            S3ResourceOutputStream os = new S3ResourceOutputStream(s3,bucketName,objectName,acl);
256                            if(append && !(barr==null || barr.length==0))
257                                    Util.copy(new ByteArrayInputStream(barr),os);
258                            return os;
259                    }
260                    catch(IOException e) {
261                            throw e;
262                    }
263                    catch (Exception e) {
264                            throw new PageRuntimeException(Caster.toPageException(e));
265                    }
266                    finally {
267                            s3.releaseCache(getInnerPath());
268                    }
269            }
270    
271            @Override
272            public Resource getRealResource(String realpath) {
273                    realpath=ResourceUtil.merge(getInnerPath(), realpath);
274                    if(realpath.startsWith("../"))return null;
275                    return new S3Resource(s3,S3.STORAGE_UNKNOW,provider,realpath,newPattern);
276            }
277    
278            @Override
279            public ResourceProvider getResourceProvider() {
280                    return provider;
281            }
282    
283            @Override
284            public boolean isDirectory() {
285                    return getInfo().isDirectory();
286            }
287    
288            @Override
289            public boolean isFile() {
290                    return getInfo().isFile();
291            }
292    
293            public boolean isReadable() {
294                    return exists();
295            }
296    
297            public boolean isWriteable() {
298                    return exists();
299            }
300    
301            @Override
302            public long lastModified() {
303                    return getInfo().getLastModified();
304            }
305    
306            private S3Info getInfo() {
307                    S3Info info = s3.getInfo(getInnerPath());
308                    
309                    if(info==null) {// || System.currentTimeMillis()>infoLastAccess
310                            if(isRoot()) {
311                                    try {
312                                            s3.listBuckets();
313                                            info=ROOT;
314                                    }
315                                    catch (Exception e) {
316                                            info=UNDEFINED;
317                                    }
318                                    infoLastAccess=FUTURE;
319                            }
320                            else {
321                                    try {
322                                            provider.read(this);
323                                    } catch (IOException e) {
324                                            return LOCKED;
325                                    }
326                                    try {   
327                                            if(isBucket()) {
328                                                    Bucket[] buckets = s3.listBuckets();
329                                                    String name=getName();
330                                                    for(int i=0;i<buckets.length;i++) {
331                                                            if(buckets[i].getName().equals(name)) {
332                                                                    info=buckets[i];
333                                                                    infoLastAccess=System.currentTimeMillis()+provider.getCache();
334                                                                    break;
335                                                            }
336                                                    }
337                                            }
338                                            else {
339                                                    try {
340                                                            // first check if the bucket exists
341                                                            // TODO not happy about this step
342                                                            Bucket[] buckets = s3.listBuckets();
343                                                            boolean bucketExists=false;
344                                                            for(int i=0;i<buckets.length;i++) {
345                                                                    if(buckets[i].getName().equals(bucketName)) {
346                                                                            bucketExists=true;
347                                                                            break;
348                                                                    }
349                                                            }
350                                                            
351                                                            if(bucketExists){
352                                                                    String path = objectName;
353                                                                    Content[] contents = s3.listContents(bucketName, path);
354                                                                    if(contents.length>0) {
355                                                                            boolean has=false;
356                                                                            for(int i=0;i<contents.length;i++) {
357                                                                                    if(ResourceUtil.translatePath(contents[i].getKey(),false,false).equals(path)) {
358                                                                                            has=true;
359                                                                                            info=contents[i];
360                                                                                            infoLastAccess=System.currentTimeMillis()+provider.getCache();
361                                                                                            break;
362                                                                                    }
363                                                                            }
364                                                                            if(!has){
365                                                                                    for(int i=0;i<contents.length;i++) {
366                                                                                            if(ResourceUtil.translatePath(contents[i].getKey(),false,false).startsWith(path)) {
367                                                                                                    info=UNDEFINED_WITH_CHILDREN;
368                                                                                                    infoLastAccess=System.currentTimeMillis()+provider.getCache();
369                                                                                                    break;
370                                                                                            }
371                                                                                    }
372                                                                            }
373                                                                    }
374                                                            }
375                                                    }
376                                                    catch(SAXException e) {
377                                                            
378                                                    }
379                                            }
380    
381                                            if(info==null){
382                                                    info=UNDEFINED;
383                                                    infoLastAccess=System.currentTimeMillis()+provider.getCache();
384                                            }
385                                    }
386                                    catch(Exception t) {
387                                            return UNDEFINED;
388                                    }
389                            }
390                            s3.setInfo(getInnerPath(), info);
391                    }
392                    return info;
393            }
394    
395            @Override
396            public long length() {
397                    return getInfo().getSize();
398            }
399    
400            public Resource[] listResources() {
401                    S3Resource[] children=null;
402                    try {
403                            if(isRoot()) {
404                                    Bucket[] buckets = s3.listBuckets();
405                                    children=new S3Resource[buckets.length];
406                                    for(int i=0;i<children.length;i++) {
407                                            children[i]=new S3Resource(s3,storage,provider,buckets[i].getName(),"",newPattern);
408                                            s3.setInfo(children[i].getInnerPath(),buckets[i]);
409                                    }
410                            }
411                            else if(isDirectory()){
412                                    Content[] contents = s3.listContents(bucketName, isBucket()?null:objectName+"/");
413                                    ArrayList<S3Resource> tmp = new ArrayList<S3Resource>();
414                                    String key,name,path;
415                                    int index;
416                                    Set<String> names=new LinkedHashSet<String>();
417                                    Set<String> pathes=new LinkedHashSet<String>();
418                                    S3Resource r;
419                                    boolean isb=isBucket();
420                                    for(int i=0;i<contents.length;i++) {
421                                            key=ResourceUtil.translatePath(contents[i].getKey(), false, false);
422                                            if(!isb && !key.startsWith(objectName+"/")) continue;
423                                            if(Util.isEmpty(key)) continue;
424                                            index=key.indexOf('/',Util.length(objectName)+1);
425                                            if(index==-1) { 
426                                                    name=key;
427                                                    path=null;
428                                            }
429                                            else {
430                                                    name=key.substring(index+1);
431                                                    path=key.substring(0,index);
432                                            }
433                                            
434                                            //print.out("1:"+key);
435                                            //print.out("path:"+path);
436                                            //print.out("name:"+name);
437                                            if(path==null){
438                                                    names.add(name);
439                                                    tmp.add(r=new S3Resource(s3,storage,provider,contents[i].getBucketName(),key,newPattern));
440                                                    s3.setInfo(r.getInnerPath(),contents[i]);
441                                            }
442                                            else {
443                                                    pathes.add(path);
444                                            }
445                                    }
446                                    
447                                    Iterator<String> it = pathes.iterator();
448                                    while(it.hasNext()) {
449                                            path=it.next();
450                                            if(names.contains(path)) continue;
451                                            tmp.add(r=new S3Resource(s3,storage,provider,bucketName,path,newPattern));
452                                            s3.setInfo(r.getInnerPath(),UNDEFINED_WITH_CHILDREN2);
453                                    }
454                                    
455                                    //if(tmp.size()==0 && !isDirectory()) return null;
456                                    
457                                    children=tmp.toArray(new S3Resource[tmp.size()]);
458                            }
459                    }
460                    catch(Exception t) {
461                            t.printStackTrace();
462                            return null;
463                    }
464                    return children;
465            }
466    
467            @Override
468            public void remove(boolean force) throws IOException {
469                    if(isRoot()) throw new IOException("can not remove root of S3 Service");
470    
471                    ResourceUtil.checkRemoveOK(this);
472                    
473                    
474                    boolean isd=isDirectory();
475                    if(isd) {
476                            Resource[] children = listResources();
477                            if(children.length>0) {
478                                    if(force) {
479                                            for(int i=0;i<children.length;i++) {
480                                                    children[i].remove(force);
481                                            }
482                                    }
483                                    else {
484                                            throw new IOException("can not remove directory ["+this+"], directory is not empty");
485                                    }
486                            }
487                    }
488                    // delete res itself
489                    provider.lock(this);
490                    try {
491                            s3.delete(bucketName, isd?objectName+"/":objectName);
492                    } 
493                    catch (Exception e) {
494                            throw new IOException(e.getMessage());
495                    }
496                    finally {
497                            s3.releaseCache(getInnerPath());
498                            provider.unlock(this);
499                    }
500                    
501                    
502            }
503    
504            public boolean setLastModified(long time) {
505                    s3.releaseCache(getInnerPath());
506                    // TODO Auto-generated method stub
507                    return false;
508            }
509    
510            public void setMode(int mode) throws IOException {
511                    s3.releaseCache(getInnerPath());
512                    // TODO Auto-generated method stub
513                    
514            }
515    
516            public boolean setReadable(boolean readable) {
517                    s3.releaseCache(getInnerPath());
518                    // TODO Auto-generated method stub
519                    return false;
520            }
521    
522            public boolean setWritable(boolean writable) {
523                    s3.releaseCache(getInnerPath());
524                    // TODO Auto-generated method stub
525                    return false;
526            }
527    
528    
529            public AccessControlPolicy getAccessControlPolicy() {
530                    String p = getInnerPath();
531                    try {
532                            AccessControlPolicy acp = s3.getACP(p);
533                            if(acp==null){
534                                    acp=s3.getAccessControlPolicy(bucketName,  getObjectName());
535                                    s3.setACP(p, acp);
536                            }
537                                    
538                            
539                            return acp;
540                    } 
541                    catch (Exception e) {
542                            throw new PageRuntimeException(Caster.toPageException(e));
543                    }
544            }
545            
546            public void setAccessControlPolicy(AccessControlPolicy acp) {
547                    
548                    try {
549                            s3.setAccessControlPolicy(bucketName, getObjectName(),acp);
550                    } 
551                    catch (Exception e) {
552                            throw new PageRuntimeException(Caster.toPageException(e));
553                    }
554                    finally {
555                            s3.releaseCache(getInnerPath());
556                    }
557            }
558            
559            private String getObjectName() {
560                    if(!StringUtil.isEmpty(objectName) && isDirectory()) {
561                            return objectName+"/";
562                    }
563                    return objectName;
564            }
565    
566    
567            public void setACL(int acl) {
568                    this.acl=acl;
569            }
570    
571    
572            public void setStorage(int storage) {
573                    this.storage=storage;
574            }
575            
576    
577    
578    }
579    
580    
581     class Dummy implements S3Info {
582    
583                    private long lastModified;
584                    private long size;
585                    private boolean exists;
586                    private boolean file;
587                    private boolean directory;
588                    private String label;
589            
590             
591            public Dummy(String label,long lastModified, long size, boolean exists,boolean file, boolean directory) {
592                    this.label = label;
593                    this.lastModified = lastModified;
594                    this.size = size;
595                    this.exists = exists;
596                    this.file = file;
597                    this.directory = directory;
598            }
599    
600    
601            @Override
602            public long getLastModified() {
603                    return lastModified;
604            }
605    
606            @Override
607            public long getSize() {
608                    return size;
609            }
610    
611            @Override
612            public String toString() {
613                    return "Dummy:"+getLabel();
614            }
615    
616    
617            /**
618             * @return the label
619             */
620            public String getLabel() {
621                    return label;
622            }
623    
624    
625            @Override
626            public boolean exists() {
627                    return exists;
628            }
629    
630            @Override
631            public boolean isDirectory() {
632                    return directory;
633            }
634    
635            @Override
636            public boolean isFile() {
637                    return file;
638            }
639    
640    }