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 }