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