package railo.commons.io.res.type.datasource; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.sql.Connection; import java.sql.SQLException; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import org.apache.commons.collections.map.ReferenceMap; import railo.commons.io.res.Resource; import railo.commons.io.res.ResourceProvider; import railo.commons.io.res.Resources; import railo.commons.io.res.type.datasource.core.Core; import railo.commons.io.res.type.datasource.core.MSSQL; import railo.commons.io.res.type.datasource.core.MySQL; import railo.commons.io.res.util.ResourceLockImpl; import railo.commons.io.res.util.ResourceUtil; import railo.commons.lang.SizeOf; import railo.commons.lang.StringUtil; import railo.runtime.config.Config; import railo.runtime.config.ConfigImpl; import railo.runtime.db.DatasourceConnection; import railo.runtime.db.DatasourceManagerImpl; import railo.runtime.engine.ThreadLocalPageContext; import railo.runtime.exp.ApplicationException; import railo.runtime.exp.DatabaseException; import railo.runtime.exp.PageException; import railo.runtime.exp.PageRuntimeException; import railo.runtime.op.Caster; import railo.runtime.type.Sizeable; /** * Resource Provider for ram resource */ public final class DatasourceResourceProvider implements ResourceProvider,Sizeable { public static final int DBTYPE_ANSI92=0; public static final int DBTYPE_MSSQL=1; public static final int DBTYPE_MYSQL=2; private static final int MAXAGE = 5000; //private static final int CONNECTION_ID = 0; private String scheme="ds"; boolean caseSensitive=true; //private Resources resources; private long lockTimeout=1000; private ResourceLockImpl lock=new ResourceLockImpl(lockTimeout,caseSensitive); private DatasourceManagerImpl _manager; private String defaultPrefix="rdr"; //private DataSourceManager manager; //private Core core; private Map cores=new WeakHashMap(); private Map attrCache=new ReferenceMap(); private Map attrsCache=new ReferenceMap(); private Map arguments; @Override public long sizeOf() { return SizeOf.size(cores)+SizeOf.size(attrCache)+SizeOf.size(attrsCache)+SizeOf.size(lock); } /** * initalize ram resource * @param scheme * @param arguments * @return RamResource */ public ResourceProvider init(String scheme,Map arguments) { if(!StringUtil.isEmpty(scheme))this.scheme=scheme; if(arguments!=null) { this.arguments=arguments; // case-sensitive Object oCaseSensitive= arguments.get("case-sensitive"); if(oCaseSensitive!=null) { caseSensitive=Caster.toBooleanValue(oCaseSensitive,true); } // prefix Object oPrefix= arguments.get("prefix"); if(oPrefix!=null) { defaultPrefix=Caster.toString(oPrefix,defaultPrefix); } // lock-timeout Object oTimeout = arguments.get("lock-timeout"); if(oTimeout!=null) { lockTimeout=Caster.toLongValue(oTimeout,lockTimeout); } } lock.setLockTimeout(lockTimeout); lock.setCaseSensitive(caseSensitive); return this; } @Override public Resource getResource(String path) { StringBuilder sb=new StringBuilder(); return new DatasourceResource(this,parse(sb,path),sb.toString()); } public ConnectionData parse(StringBuilder subPath,String path) { path=ResourceUtil.removeScheme(scheme,path); ConnectionData data=new ConnectionData(); int atIndex=path.indexOf('@'); int slashIndex=path.indexOf('/'); if(slashIndex==-1){ slashIndex=path.length(); path+="/"; } int index; // username/password if(atIndex!=-1) { index=path.indexOf(':'); if(index!=-1 && index<atIndex) { data.setUsername(path.substring(0,index)); data.setPassword(path.substring(index+1,atIndex)); } else data.setUsername(path.substring(0,atIndex)); } // host port if(slashIndex>atIndex+1) { data.setDatasourceName(path.substring(atIndex+1,slashIndex)); } if(slashIndex>atIndex+1) { index=path.indexOf(':',atIndex+1); if(index!=-1 && index>atIndex && index<slashIndex) { data.setDatasourceName(path.substring(atIndex+1,index)); data.setPrefix(path.substring(index+1,slashIndex)); } else { data.setDatasourceName(path.substring(atIndex+1,slashIndex)); data.setPrefix(defaultPrefix); } } subPath.append(path.substring(slashIndex)); return data; } @Override public String getScheme() { return scheme; } @Override public void setResources(Resources resources) { //this.resources=resources; } @Override public void lock(Resource res) throws IOException { lock.lock(res); } @Override public void unlock(Resource res) { lock.unlock(res); } @Override public void read(Resource res) throws IOException { lock.read(res); } @Override public boolean isAttributesSupported() { return false; } @Override public boolean isCaseSensitive() { return caseSensitive; } @Override public boolean isModeSupported() { return true; } private DatasourceManagerImpl getManager() { if(_manager==null){ Config config = ThreadLocalPageContext.getConfig(); _manager=new DatasourceManagerImpl((ConfigImpl) config); } return _manager; } private Core getCore(ConnectionData data) throws PageException{ Core core = (Core) cores.get(data.datasourceName); if(core==null){ DatasourceConnection dc = getManager().getConnection(ThreadLocalPageContext.get(), data.getDatasourceName(), data.getUsername(), data.getPassword()); try { dc.getConnection().setAutoCommit(false); dc.getConnection().setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); if("com.microsoft.jdbc.sqlserver.SQLServerDriver".equals(dc.getDatasource().getClazz().getName())) core=new MSSQL(dc,data.getPrefix()); else if("com.microsoft.sqlserver.jdbc.SQLServerDriver".equals(dc.getDatasource().getClazz().getName())) core=new MSSQL(dc,data.getPrefix()); else if("net.sourceforge.jtds.jdbc.Driver".equals(dc.getDatasource().getClazz().getName())) core=new MSSQL(dc,data.getPrefix()); else if("org.gjt.mm.mysql.Driver".equals(dc.getDatasource().getClazz().getName())) core=new MySQL(dc,data.getPrefix()); else throw new ApplicationException("there is no DatasourceResource driver for this database ["+data.getPrefix()+"]"); cores.put(data.datasourceName, core); } catch(SQLException e) { throw new DatabaseException(e,dc); } finally { release(dc); //manager.releaseConnection(CONNECTION_ID,dc); } } return core; } private DatasourceConnection getDatasourceConnection(ConnectionData data, boolean autoCommit) throws PageException { DatasourceConnection dc = getManager().getConnection(ThreadLocalPageContext.get(), data.getDatasourceName(), data.getUsername(), data.getPassword()); try { dc.getConnection().setAutoCommit(autoCommit); dc.getConnection().setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); } catch (SQLException e) { throw new DatabaseException(e,dc); } return dc; } private DatasourceConnection getDatasourceConnection(ConnectionData data) throws PageException { return getDatasourceConnection(data,false); } public Attr getAttr(ConnectionData data, int fullPathHash,String path, String name) { Attr attr=getFromCache(data,path,name); if(attr!=null) return attr; try { return _getAttr(data, fullPathHash,path, name); } catch (PageException pe) { throw new PageRuntimeException(pe); } } private Attr _getAttr(ConnectionData data, int fullPathHash,String path, String name) throws PageException { if(!StringUtil.isEmpty(data.getDatasourceName())) { DatasourceConnection dc=null; try { dc = getDatasourceConnection(data); Attr attr=getCore(data).getAttr(dc,data.getPrefix(),fullPathHash,path,name); if(attr!=null)return putToCache(data,path,name, attr); } catch (SQLException e) { throw new DatabaseException(e,dc); } finally { getManager().releaseConnection(ThreadLocalPageContext.get(),dc); } } return putToCache(data,path,name,Attr.notExists(name,path)); } public Attr[] getAttrs(ConnectionData data, int pathHash,String path) throws PageException { if(StringUtil.isEmpty(data.getDatasourceName())) return null; //Attr[] attrs = getFromCache(data, path); //if(attrs!=null) return attrs; DatasourceConnection dc=null; try { dc = getDatasourceConnection(data); List list=getCore(data).getAttrs(dc,data.getPrefix(),pathHash,path); if(list!=null){ Iterator it = list.iterator(); Attr[] rtn=new Attr[list.size()]; int index=0; while(it.hasNext()) { rtn[index]=(Attr) it.next(); putToCache(data,rtn[index].getParent(),rtn[index].getName(),rtn[index]); index++; } //putToCache(data, path, rtn); return rtn; } } catch (SQLException e) { throw new DatabaseException(e,dc); } finally { release(dc); //manager.releaseConnection(CONNECTION_ID,dc); } return null; } public void create(ConnectionData data, int fullPathHash,int pathHash,String path, String name, int type) throws IOException { if(StringUtil.isEmpty(data.getDatasourceName())) throw new IOException("missing datasource definition"); removeFromCache(data, path, name); DatasourceConnection dc=null; try { dc = getDatasourceConnection(data); getCore(data).create(dc,data.getPrefix(),fullPathHash, pathHash,path,name,type); } catch (SQLException e) { throw new IOException(e.getMessage()); } catch (PageException e) { throw new PageRuntimeException(e); } finally { release(dc); } } public void delete(ConnectionData data, int fullPathHash,String path, String name) throws IOException { Attr attr = getAttr(data, fullPathHash,path, name); if(attr==null) throw new IOException("can't delete resource "+path+name+", resource does not exist"); DatasourceConnection dc=null; try { dc = getDatasourceConnection(data); getCore(data).delete(dc,data.getPrefix(),attr); } catch (SQLException e) { throw new IOException(e.getMessage()); } catch (PageException e) { throw new PageRuntimeException(e); } finally { removeFromCache(data, path, name); release(dc); //manager.releaseConnection(CONNECTION_ID,dc); } } public InputStream getInputStream(ConnectionData data, int fullPathHash, String path,String name) throws IOException { Attr attr = getAttr(data, fullPathHash,path, name); if(attr==null) throw new IOException("file ["+path+name+"] does not exist"); DatasourceConnection dc=null; try { dc = getDatasourceConnection(data); return getCore(data).getInputStream(dc, data.getPrefix(), attr); } catch (SQLException e) { throw new IOException(e.getMessage()); } catch (PageException e) { throw new PageRuntimeException(e); } finally { release(dc); //manager.releaseConnection(CONNECTION_ID,dc); } } public synchronized OutputStream getOutputStream(ConnectionData data, int fullPathHash, int pathHash,String path,String name,boolean append) throws IOException { Attr attr = getAttr(data, fullPathHash,path, name); if(attr.getId()==0){ create(data, fullPathHash, pathHash, path, name, Attr.TYPE_FILE); attr = getAttr(data, fullPathHash,path, name); } PipedInputStream pis = new PipedInputStream(); PipedOutputStream pos = new PipedOutputStream(); pis.connect(pos); DatasourceConnection dc=null; //Connection c=null; try { dc = getDatasourceConnection(data); //Connection c = dc.getConnection(); DataWriter writer=new DataWriter(getCore(data),dc, data.getPrefix(), attr, pis,this,append); writer.start(); return new DatasourceResourceOutputStream(writer,pos); //core.getOutputStream(dc, name, attr, pis); } catch (PageException e) { throw new PageRuntimeException(e); } finally { removeFromCache(data, path, name); //manager.releaseConnection(CONNECTION_ID,dc); } } public boolean setLastModified(ConnectionData data, int fullPathHash,String path,String name,long time) { try { Attr attr = getAttr(data, fullPathHash,path, name); DatasourceConnection dc = getDatasourceConnection(data); try { getCore(data).setLastModified(dc,data.getPrefix(),attr,time); } /*catch (SQLException e) { return false; } */ finally { removeFromCache(data, path, name); release(dc); //manager.releaseConnection(CONNECTION_ID,dc); } } catch(Throwable t) { return false; } return true; } public boolean setMode(ConnectionData data, int fullPathHash,String path,String name,int mode) { try { Attr attr = getAttr(data, fullPathHash, path, name); DatasourceConnection dc = getDatasourceConnection(data); try { getCore(data).setMode(dc,data.getPrefix(),attr,mode); } /*catch (SQLException e) { return false; } */ finally { removeFromCache(data, path, name); release(dc); //manager.releaseConnection(CONNECTION_ID,dc); } } catch(Throwable t) { return false; } return true; } public boolean concatSupported(ConnectionData data) { try { return getCore(data).concatSupported(); } catch (PageException e) { return false; } } private Attr removeFromCache(ConnectionData data, String path,String name) { attrsCache.remove(data.key()+path); return (Attr) attrCache.remove(data.key()+path+name); } private Attr getFromCache(ConnectionData data, String path,String name) { String key=data.key()+path+name; Attr attr=(Attr) attrCache.get(key); if(attr!=null && attr.timestamp()+MAXAGE<System.currentTimeMillis()) { attrCache.remove(key); return null; } return attr; } private Attr putToCache(ConnectionData data, String path,String name, Attr attr) { attrCache.put(data.key()+path+name, attr); return attr; } /*private Attr[] getFromCache(ConnectionData data, String path) { String key=data.key()+path; Attr[] attrs= (Attr[]) attrsCache.get(key); / *if(attr!=null && attr.timestamp()+MAXAGE<System.currentTimeMillis()) { attrCache.remove(key); return null; }* / return attrs; } private Attr[] putToCache(ConnectionData data, String path, Attr[] attrs) { attrsCache.put(data.key()+path, attrs); return attrs; }*/ public class ConnectionData { private String username; private String password; private String datasourceName; private String prefix; /** * @return the prefix */ public String getPrefix() { return prefix; } /** * @param prefix the prefix to set */ public void setPrefix(String prefix) { this.prefix = prefix; } /** * @return the username */ public String getUsername() { return username; } /** * @param username the username to set */ public void setUsername(String username) { this.username = username; } /** * @return the password */ public String getPassword() { return password; } /** * @param password the password to set */ public void setPassword(String password) { this.password = password; } /** * @return the datasourceName */ public String getDatasourceName() { return datasourceName; } /** * @param datasourceName the datasourceName to set */ public void setDatasourceName(String datasourceName) { this.datasourceName = datasourceName; } public String key() { if(StringUtil.isEmpty(username)) return datasourceName; return username+":"+password+"@"+datasourceName; } } /** * release datasource connection * @param dc * @param autoCommit */ void release(DatasourceConnection dc) { if(dc!=null) { try { dc.getConnection().commit(); dc.getConnection().setAutoCommit(true); dc.getConnection().setTransactionIsolation(Connection.TRANSACTION_NONE); } catch (SQLException e) {} getManager().releaseConnection(ThreadLocalPageContext.get(),dc); } } @Override public Map getArguments() { return arguments; } }