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 }