001 package railo.commons.io.res.type.datasource; 002 003 import java.io.IOException; 004 import java.io.InputStream; 005 import java.io.OutputStream; 006 import java.io.PipedInputStream; 007 import java.io.PipedOutputStream; 008 import java.sql.Connection; 009 import java.sql.SQLException; 010 import java.util.Iterator; 011 import java.util.List; 012 import java.util.Map; 013 import java.util.WeakHashMap; 014 015 import org.apache.commons.collections.map.ReferenceMap; 016 017 import railo.commons.io.res.Resource; 018 import railo.commons.io.res.ResourceProvider; 019 import railo.commons.io.res.Resources; 020 import railo.commons.io.res.type.datasource.core.Core; 021 import railo.commons.io.res.type.datasource.core.MSSQL; 022 import railo.commons.io.res.type.datasource.core.MySQL; 023 import railo.commons.io.res.util.ResourceLockImpl; 024 import railo.commons.io.res.util.ResourceUtil; 025 import railo.commons.lang.SizeOf; 026 import railo.commons.lang.StringUtil; 027 import railo.runtime.config.Config; 028 import railo.runtime.config.ConfigImpl; 029 import railo.runtime.db.DatasourceConnection; 030 import railo.runtime.db.DatasourceManagerImpl; 031 import railo.runtime.engine.ThreadLocalPageContext; 032 import railo.runtime.exp.ApplicationException; 033 import railo.runtime.exp.DatabaseException; 034 import railo.runtime.exp.PageException; 035 import railo.runtime.exp.PageRuntimeException; 036 import railo.runtime.op.Caster; 037 import railo.runtime.type.Sizeable; 038 039 040 /** 041 * Resource Provider for ram resource 042 */ 043 public final class DatasourceResourceProvider implements ResourceProvider,Sizeable { 044 045 public static final int DBTYPE_ANSI92=0; 046 public static final int DBTYPE_MSSQL=1; 047 public static final int DBTYPE_MYSQL=2; 048 049 private static final int MAXAGE = 5000; 050 051 //private static final int CONNECTION_ID = 0; 052 053 private String scheme="ds"; 054 055 boolean caseSensitive=true; 056 //private Resources resources; 057 private long lockTimeout=1000; 058 private ResourceLockImpl lock=new ResourceLockImpl(lockTimeout,caseSensitive); 059 private DatasourceManagerImpl _manager; 060 private String defaultPrefix="rdr"; 061 //private DataSourceManager manager; 062 //private Core core; 063 private Map cores=new WeakHashMap(); 064 private Map attrCache=new ReferenceMap(); 065 private Map attrsCache=new ReferenceMap(); 066 private Map arguments; 067 068 069 /** 070 * @see railo.runtime.type.Sizeable#sizeOf() 071 */ 072 public long sizeOf() { 073 return SizeOf.size(cores)+SizeOf.size(attrCache)+SizeOf.size(attrsCache)+SizeOf.size(lock); 074 } 075 076 077 078 079 /** 080 * initalize ram resource 081 * @param scheme 082 * @param arguments 083 * @return RamResource 084 */ 085 public ResourceProvider init(String scheme,Map arguments) { 086 if(!StringUtil.isEmpty(scheme))this.scheme=scheme; 087 088 if(arguments!=null) { 089 this.arguments=arguments; 090 // case-sensitive 091 Object oCaseSensitive= arguments.get("case-sensitive"); 092 if(oCaseSensitive!=null) { 093 caseSensitive=Caster.toBooleanValue(oCaseSensitive,true); 094 } 095 096 // prefix 097 Object oPrefix= arguments.get("prefix"); 098 if(oPrefix!=null) { 099 defaultPrefix=Caster.toString(oPrefix,defaultPrefix); 100 } 101 102 // lock-timeout 103 Object oTimeout = arguments.get("lock-timeout"); 104 if(oTimeout!=null) { 105 lockTimeout=Caster.toLongValue(oTimeout,lockTimeout); 106 } 107 } 108 lock.setLockTimeout(lockTimeout); 109 lock.setCaseSensitive(caseSensitive); 110 return this; 111 } 112 113 /** 114 * @see res.ResourceProvider#getResource(java.lang.String) 115 */ 116 public Resource getResource(String path) { 117 // ds://[ username [: password ]@]datasource/dir/file.cfm 118 StringBuilder sb=new StringBuilder(); 119 return new DatasourceResource(this,parse(sb,path),sb.toString()); 120 } 121 122 123 public ConnectionData parse(StringBuilder subPath,String path) { 124 path=ResourceUtil.removeScheme(scheme,path); 125 126 ConnectionData data=new ConnectionData(); 127 int atIndex=path.indexOf('@'); 128 int slashIndex=path.indexOf('/'); 129 if(slashIndex==-1){ 130 slashIndex=path.length(); 131 path+="/"; 132 } 133 int index; 134 135 // username/password 136 if(atIndex!=-1) { 137 index=path.indexOf(':'); 138 if(index!=-1 && index<atIndex) { 139 data.setUsername(path.substring(0,index)); 140 data.setPassword(path.substring(index+1,atIndex)); 141 } 142 else data.setUsername(path.substring(0,atIndex)); 143 } 144 // host port 145 if(slashIndex>atIndex+1) { 146 data.setDatasourceName(path.substring(atIndex+1,slashIndex)); 147 } 148 if(slashIndex>atIndex+1) { 149 index=path.indexOf(':',atIndex+1); 150 if(index!=-1 && index>atIndex && index<slashIndex) { 151 data.setDatasourceName(path.substring(atIndex+1,index)); 152 data.setPrefix(path.substring(index+1,slashIndex)); 153 } 154 else { 155 data.setDatasourceName(path.substring(atIndex+1,slashIndex)); 156 data.setPrefix(defaultPrefix); 157 } 158 } 159 subPath.append(path.substring(slashIndex)); 160 return data; 161 } 162 163 164 /** 165 * @see res.ResourceProvider#getScheme() 166 */ 167 public String getScheme() { 168 return scheme; 169 } 170 /** 171 * @see railo.commons.io.res.ResourceProvider#setResources(railo.commons.io.res.Resources) 172 */ 173 public void setResources(Resources resources) { 174 //this.resources=resources; 175 } 176 177 /** 178 * @throws IOException 179 * @see railo.commons.io.res.ResourceProvider#lock(railo.commons.io.res.Resource) 180 */ 181 public void lock(Resource res) throws IOException { 182 lock.lock(res); 183 } 184 185 /** 186 * @see railo.commons.io.res.ResourceProvider#unlock(railo.commons.io.res.Resource) 187 */ 188 public void unlock(Resource res) { 189 lock.unlock(res); 190 } 191 192 /** 193 * @see railo.commons.io.res.ResourceProvider#read(railo.commons.io.res.Resource) 194 */ 195 public void read(Resource res) throws IOException { 196 lock.read(res); 197 } 198 199 /** 200 * 201 * @see railo.commons.io.res.ResourceProvider#isAttributesSupported() 202 */ 203 public boolean isAttributesSupported() { 204 return false; 205 } 206 207 /** 208 * 209 * @see railo.commons.io.res.ResourceProvider#isCaseSensitive() 210 */ 211 public boolean isCaseSensitive() { 212 return caseSensitive; 213 } 214 215 /** 216 * 217 * @see railo.commons.io.res.ResourceProvider#isModeSupported() 218 */ 219 public boolean isModeSupported() { 220 return true; 221 } 222 223 private DatasourceManagerImpl getManager() { 224 if(_manager==null){ 225 Config config = ThreadLocalPageContext.getConfig(); 226 _manager=new DatasourceManagerImpl((ConfigImpl) config); 227 } 228 return _manager; 229 } 230 231 private Core getCore(ConnectionData data) throws PageException{ 232 Core core = (Core) cores.get(data.datasourceName); 233 if(core==null){ 234 DatasourceConnection dc = getManager().getConnection(ThreadLocalPageContext.get(), data.getDatasourceName(), data.getUsername(), data.getPassword()); 235 try { 236 237 dc.getConnection().setAutoCommit(false); 238 dc.getConnection().setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); 239 240 if("com.microsoft.jdbc.sqlserver.SQLServerDriver".equals(dc.getDatasource().getClazz().getName())) 241 core=new MSSQL(dc,data.getPrefix()); 242 else if("com.microsoft.sqlserver.jdbc.SQLServerDriver".equals(dc.getDatasource().getClazz().getName())) 243 core=new MSSQL(dc,data.getPrefix()); 244 else if("net.sourceforge.jtds.jdbc.Driver".equals(dc.getDatasource().getClazz().getName())) 245 core=new MSSQL(dc,data.getPrefix()); 246 else if("org.gjt.mm.mysql.Driver".equals(dc.getDatasource().getClazz().getName())) 247 core=new MySQL(dc,data.getPrefix()); 248 else 249 throw new ApplicationException("there is no DatasourceResource driver for this database ["+data.getPrefix()+"]"); 250 251 cores.put(data.datasourceName, core); 252 } 253 catch(SQLException e) { 254 throw new DatabaseException(e,dc); 255 } 256 finally { 257 release(dc); 258 //manager.releaseConnection(CONNECTION_ID,dc); 259 } 260 } 261 return core; 262 } 263 264 private DatasourceConnection getDatasourceConnection(ConnectionData data, boolean autoCommit) throws PageException { 265 DatasourceConnection dc = getManager().getConnection(ThreadLocalPageContext.get(), data.getDatasourceName(), data.getUsername(), data.getPassword()); 266 267 try { 268 dc.getConnection().setAutoCommit(autoCommit); 269 dc.getConnection().setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); 270 } 271 catch (SQLException e) { 272 throw new DatabaseException(e,dc); 273 } 274 275 return dc; 276 } 277 278 private DatasourceConnection getDatasourceConnection(ConnectionData data) throws PageException { 279 return getDatasourceConnection(data,false); 280 } 281 282 public Attr getAttr(ConnectionData data, int fullPathHash,String path, String name) { 283 Attr attr=getFromCache(data,path,name); 284 if(attr!=null) return attr; 285 try { 286 return _getAttr(data, fullPathHash,path, name); 287 } 288 catch (PageException pe) { 289 throw new PageRuntimeException(pe); 290 } 291 } 292 293 private Attr _getAttr(ConnectionData data, int fullPathHash,String path, String name) throws PageException { 294 if(!StringUtil.isEmpty(data.getDatasourceName())) { 295 DatasourceConnection dc=null; 296 try { 297 dc = getDatasourceConnection(data); 298 Attr attr=getCore(data).getAttr(dc,data.getPrefix(),fullPathHash,path,name); 299 if(attr!=null)return putToCache(data,path,name, attr); 300 } 301 catch (SQLException e) { 302 throw new DatabaseException(e,dc); 303 } 304 finally { 305 getManager().releaseConnection(ThreadLocalPageContext.get(),dc); 306 } 307 } 308 return putToCache(data,path,name,Attr.notExists(name,path)); 309 } 310 311 public Attr[] getAttrs(ConnectionData data, int pathHash,String path) throws PageException { 312 if(StringUtil.isEmpty(data.getDatasourceName())) 313 return null; 314 315 //Attr[] attrs = getFromCache(data, path); 316 //if(attrs!=null) return attrs; 317 318 DatasourceConnection dc=null; 319 try { 320 dc = getDatasourceConnection(data); 321 List list=getCore(data).getAttrs(dc,data.getPrefix(),pathHash,path); 322 323 if(list!=null){ 324 Iterator it = list.iterator(); 325 Attr[] rtn=new Attr[list.size()]; 326 int index=0; 327 while(it.hasNext()) { 328 rtn[index]=(Attr) it.next(); 329 putToCache(data,rtn[index].getParent(),rtn[index].getName(),rtn[index]); 330 index++; 331 } 332 //putToCache(data, path, rtn); 333 return rtn; 334 } 335 } 336 catch (SQLException e) { 337 throw new DatabaseException(e,dc); 338 } 339 finally { 340 release(dc); 341 //manager.releaseConnection(CONNECTION_ID,dc); 342 } 343 return null; 344 } 345 346 public void create(ConnectionData data, int fullPathHash,int pathHash,String path, String name, int type) throws IOException { 347 if(StringUtil.isEmpty(data.getDatasourceName())) 348 throw new IOException("missing datasource definition"); 349 350 removeFromCache(data, path, name); 351 352 353 DatasourceConnection dc=null; 354 try { 355 dc = getDatasourceConnection(data); 356 getCore(data).create(dc,data.getPrefix(),fullPathHash, pathHash,path,name,type); 357 } 358 catch (SQLException e) { 359 throw new IOException(e.getMessage()); 360 } 361 catch (PageException e) { 362 throw new PageRuntimeException(e); 363 } 364 finally { 365 release(dc); 366 } 367 } 368 369 370 public void delete(ConnectionData data, int fullPathHash,String path, String name) throws IOException { 371 372 Attr attr = getAttr(data, fullPathHash,path, name); 373 if(attr==null) throw new IOException("can't delete resource "+path+name+", resource does not exists"); 374 375 DatasourceConnection dc=null; 376 try { 377 dc = getDatasourceConnection(data); 378 getCore(data).delete(dc,data.getPrefix(),attr); 379 } 380 catch (SQLException e) { 381 throw new IOException(e.getMessage()); 382 } 383 catch (PageException e) { 384 throw new PageRuntimeException(e); 385 } 386 finally { 387 removeFromCache(data, path, name); 388 release(dc); 389 //manager.releaseConnection(CONNECTION_ID,dc); 390 } 391 } 392 393 public InputStream getInputStream(ConnectionData data, int fullPathHash, String path,String name) throws IOException { 394 Attr attr = getAttr(data, fullPathHash,path, name); 395 if(attr==null) throw new IOException("file ["+path+name+"] does not exists"); 396 DatasourceConnection dc=null; 397 try { 398 dc = getDatasourceConnection(data); 399 return getCore(data).getInputStream(dc, data.getPrefix(), attr); 400 } 401 catch (SQLException e) { 402 throw new IOException(e.getMessage()); 403 } 404 catch (PageException e) { 405 throw new PageRuntimeException(e); 406 } 407 finally { 408 release(dc); 409 //manager.releaseConnection(CONNECTION_ID,dc); 410 } 411 } 412 413 public synchronized OutputStream getOutputStream(ConnectionData data, int fullPathHash, int pathHash,String path,String name,boolean append) throws IOException { 414 415 Attr attr = getAttr(data, fullPathHash,path, name); 416 if(attr.getId()==0){ 417 create(data, fullPathHash, pathHash, path, name, Attr.TYPE_FILE); 418 attr = getAttr(data, fullPathHash,path, name); 419 } 420 421 422 //InputStream is = new ByteArrayInputStream("susi".getBytes()); 423 424 PipedInputStream pis = new PipedInputStream(); 425 PipedOutputStream pos = new PipedOutputStream(); 426 pis.connect(pos); 427 DatasourceConnection dc=null; 428 //Connection c=null; 429 try { 430 dc = getDatasourceConnection(data); 431 //Connection c = dc.getConnection(); 432 433 DataWriter writer=new DataWriter(getCore(data),dc, data.getPrefix(), attr, pis,this,append); 434 writer.start(); 435 436 return new DatasourceResourceOutputStream(writer,pos); 437 //core.getOutputStream(dc, name, attr, pis); 438 } 439 catch (PageException e) { 440 throw new PageRuntimeException(e); 441 } 442 finally { 443 removeFromCache(data, path, name); 444 //manager.releaseConnection(CONNECTION_ID,dc); 445 } 446 } 447 448 449 public boolean setLastModified(ConnectionData data, int fullPathHash,String path,String name,long time) { 450 try { 451 Attr attr = getAttr(data, fullPathHash,path, name); 452 DatasourceConnection dc = getDatasourceConnection(data); 453 try { 454 getCore(data).setLastModified(dc,data.getPrefix(),attr,time); 455 } 456 /*catch (SQLException e) { 457 return false; 458 } */ 459 finally { 460 removeFromCache(data, path, name); 461 release(dc); 462 //manager.releaseConnection(CONNECTION_ID,dc); 463 } 464 } 465 catch(Throwable t) { 466 return false; 467 } 468 return true; 469 } 470 471 public boolean setMode(ConnectionData data, int fullPathHash,String path,String name,int mode) { 472 try { 473 Attr attr = getAttr(data, fullPathHash, path, name); 474 DatasourceConnection dc = getDatasourceConnection(data); 475 try { 476 getCore(data).setMode(dc,data.getPrefix(),attr,mode); 477 } 478 /*catch (SQLException e) { 479 return false; 480 } */ 481 finally { 482 removeFromCache(data, path, name); 483 release(dc); 484 //manager.releaseConnection(CONNECTION_ID,dc); 485 } 486 } 487 catch(Throwable t) { 488 return false; 489 } 490 return true; 491 } 492 493 public boolean concatSupported(ConnectionData data) { 494 try { 495 return getCore(data).concatSupported(); 496 } catch (PageException e) { 497 return false; 498 } 499 } 500 501 private Attr removeFromCache(ConnectionData data, String path,String name) { 502 attrsCache.remove(data.key()+path); 503 return (Attr) attrCache.remove(data.key()+path+name); 504 } 505 506 private Attr getFromCache(ConnectionData data, String path,String name) { 507 String key=data.key()+path+name; 508 Attr attr=(Attr) attrCache.get(key); 509 510 if(attr!=null && attr.timestamp()+MAXAGE<System.currentTimeMillis()) { 511 attrCache.remove(key); 512 return null; 513 } 514 return attr; 515 } 516 517 private Attr putToCache(ConnectionData data, String path,String name, Attr attr) { 518 attrCache.put(data.key()+path+name, attr); 519 return attr; 520 } 521 522 523 524 /*private Attr[] getFromCache(ConnectionData data, String path) { 525 String key=data.key()+path; 526 Attr[] attrs= (Attr[]) attrsCache.get(key); 527 528 / *if(attr!=null && attr.timestamp()+MAXAGE<System.currentTimeMillis()) { 529 attrCache.remove(key); 530 return null; 531 }* / 532 return attrs; 533 } 534 535 private Attr[] putToCache(ConnectionData data, String path, Attr[] attrs) { 536 attrsCache.put(data.key()+path, attrs); 537 return attrs; 538 }*/ 539 540 541 542 public class ConnectionData { 543 private String username; 544 private String password; 545 private String datasourceName; 546 private String prefix; 547 /** 548 * @return the prefix 549 */ 550 public String getPrefix() { 551 return prefix; 552 } 553 554 /** 555 * @param prefix the prefix to set 556 */ 557 public void setPrefix(String prefix) { 558 this.prefix = prefix; 559 } 560 561 /** 562 * @return the username 563 */ 564 public String getUsername() { 565 return username; 566 } 567 568 /** 569 * @param username the username to set 570 */ 571 public void setUsername(String username) { 572 this.username = username; 573 } 574 /** 575 * @return the password 576 */ 577 public String getPassword() { 578 return password; 579 } 580 /** 581 * @param password the password to set 582 */ 583 public void setPassword(String password) { 584 this.password = password; 585 } 586 /** 587 * @return the datasourceName 588 */ 589 public String getDatasourceName() { 590 return datasourceName; 591 } 592 /** 593 * @param datasourceName the datasourceName to set 594 */ 595 public void setDatasourceName(String datasourceName) { 596 this.datasourceName = datasourceName; 597 } 598 599 public String key() { 600 if(StringUtil.isEmpty(username)) 601 return datasourceName; 602 return username+":"+password+"@"+datasourceName; 603 } 604 605 } 606 607 608 609 /** 610 * release datasource connection 611 * @param dc 612 * @param autoCommit 613 */ 614 void release(DatasourceConnection dc) { 615 if(dc!=null) { 616 617 try { 618 dc.getConnection().commit(); 619 dc.getConnection().setAutoCommit(true); 620 dc.getConnection().setTransactionIsolation(Connection.TRANSACTION_NONE); 621 } 622 catch (SQLException e) {} 623 624 getManager().releaseConnection(ThreadLocalPageContext.get(),dc); 625 } 626 } 627 628 /** 629 * @see railo.commons.io.res.ResourceProvider#getArguments() 630 */ 631 public Map getArguments() { 632 return arguments; 633 } 634 635 636 637 638 639 640 641 }