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