/** * Copyright (c) 2014, the Railo Company Ltd. * Copyright (c) 2015, Lucee Assosication Switzerland * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see <http://www.gnu.org/licenses/>. * */ package lucee.runtime.db; import java.sql.Connection; import java.sql.SQLException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import lucee.runtime.PageContext; import lucee.runtime.PageContextImpl; import lucee.runtime.config.ConfigImpl; import lucee.runtime.engine.ThreadLocalPageContext; import lucee.runtime.exp.DatabaseException; import lucee.runtime.exp.DeprecatedException; import lucee.runtime.exp.ExceptionHandler; import lucee.runtime.exp.PageException; import lucee.runtime.exp.PageRuntimeException; import lucee.runtime.orm.ORMDatasourceConnection; import lucee.runtime.orm.ORMSession; /** * this class handle multible db connection, transaction and logging */ public final class DatasourceManagerImpl implements DataSourceManager { public static final String QOQ_DATASOURCE_NAME = "_queryofquerydb"; private ConfigImpl config; boolean autoCommit=true; private int isolation=Connection.TRANSACTION_NONE; private Map<DataSource,DatasourceConnection> transConns=new HashMap<DataSource,DatasourceConnection>(); private boolean inside; /** * constructor of the class * @param pc */ public DatasourceManagerImpl(ConfigImpl c) { this.config=c; } @Override public DatasourceConnection getConnection(PageContext pc,String _datasource, String user, String pass) throws PageException { return getConnection(pc,pc.getDataSource(_datasource), user, pass); } @Override public DatasourceConnection getConnection(PageContext pc,DataSource ds, String user, String pass) throws PageException { if(autoCommit) { return config.getDatasourceConnectionPool().getDatasourceConnection(ThreadLocalPageContext.getConfig(pc),ds,user,pass); } pc=ThreadLocalPageContext.get(pc); //DatasourceConnection newDC = _getConnection(pc,ds,user,pass); DatasourceConnectionPro existingDC=null; try { existingDC = (DatasourceConnectionPro) transConns.get(ds); // first time that datasource is used within this transaction if(existingDC==null) { DatasourceConnection newDC=config.getDatasourceConnectionPool().getDatasourceConnection(config,ds, user, pass); newDC.getConnection().setAutoCommit(false); if(isolation!=Connection.TRANSACTION_NONE) newDC.getConnection().setTransactionIsolation(isolation); transConns.put(ds, newDC); return newDC; } // we have already the same datasource but with different credentials if(!DatasourceConnectionImpl.equals(existingDC,ds,user,pass)) { if(QOQ_DATASOURCE_NAME.equalsIgnoreCase(ds.getName())) return existingDC; throw new DatabaseException("can't use different connections to the same datasource inside a single transaction.",null,null,existingDC); } // make sure we have auto commit disabled TODO i dont think this is necessary anymore if(existingDC.isAutoCommit()) { existingDC.setAutoCommit(false); } return existingDC; } catch (SQLException e) { throw new DatabaseException(e, null, existingDC); //ExceptionHandler.printStackTrace(e); } //return newDC; } public void add(PageContext pc,ORMSession session) throws PageException { if(autoCommit || inside) return; inside=true; try{ DataSource[] sources = session.getDataSources(); for(int i=0;i<sources.length;i++){ _add(pc,session,sources[i]); } } finally { inside=false; } } private void _add(PageContext pc,ORMSession session, DataSource ds) throws PageException { //ORMDatasourceConnection newDC = new ORMDatasourceConnection(pc,session,ds); DatasourceConnectionPro existingDC=null; try { existingDC = (DatasourceConnectionPro) transConns.get(ds); // if(existingDC==null) { if(isolation==Connection.TRANSACTION_NONE) isolation=Connection.TRANSACTION_SERIALIZABLE; ORMDatasourceConnection newDC = new ORMDatasourceConnection(pc,session,ds,isolation); transConns.put(ds,newDC); return; } if(!DatasourceConnectionImpl.equals(existingDC,ds,null,null)) { //releaseConnection(pc,newDC); throw new DatabaseException( "can't use different connections to the same datasource inside a single transaction",null,null,existingDC); } if(existingDC.isAutoCommit()) { existingDC.setAutoCommit(false); } return; } catch (SQLException e) { //ExceptionHandler.printStackTrace(e); throw new DatabaseException(e, null, existingDC); } } @Override public void releaseConnection(PageContext pc,DatasourceConnection dc) { if(autoCommit) { config.getDatasourceConnectionPool().releaseDatasourceConnection(dc,pc!=null && ((PageContextImpl)pc).getTimeoutStackTrace()!=null); } } @Override public void begin() { this.autoCommit=false; this.isolation=Connection.TRANSACTION_NONE; } @Override public void begin(String isolation) { this.autoCommit=false; if(isolation.equalsIgnoreCase("read_uncommitted")) this.isolation=Connection.TRANSACTION_READ_UNCOMMITTED; else if(isolation.equalsIgnoreCase("read_committed")) this.isolation=Connection.TRANSACTION_READ_COMMITTED; else if(isolation.equalsIgnoreCase("repeatable_read")) this.isolation=Connection.TRANSACTION_REPEATABLE_READ; else if(isolation.equalsIgnoreCase("serializable")) this.isolation=Connection.TRANSACTION_SERIALIZABLE; else this.isolation=Connection.TRANSACTION_NONE; } @Override public void begin(int isolation) { //print.out("begin:"+autoCommit); this.autoCommit=false; this.isolation=isolation; } @Override public void rollback() throws DatabaseException { if(autoCommit || transConns.size()==0)return; Iterator<DatasourceConnection> it = this.transConns.values().iterator(); DatasourceConnection dc=null; try { while(it.hasNext()){ dc= it.next(); dc.getConnection().rollback(); } } catch (SQLException e) { throw new DatabaseException(e,dc); } } @Override public void savepoint() throws DatabaseException { if(autoCommit || transConns.size()==0)return; Iterator<DatasourceConnection> it = this.transConns.values().iterator(); DatasourceConnection dc=null; try { while(it.hasNext()){ dc= it.next(); dc.getConnection().setSavepoint(); } } catch (SQLException e) { throw new DatabaseException(e,dc); } } @Override public void commit() throws DatabaseException { if(autoCommit || transConns.size()==0)return ; Iterator<DatasourceConnection> it = this.transConns.values().iterator(); DatasourceConnection dc=null; try { while(it.hasNext()){ dc= it.next(); dc.getConnection().commit(); } } catch (SQLException e) { throw new DatabaseException(e,dc); } } @Override public boolean isAutoCommit() { return autoCommit; } @Override public void remove(DataSource datasource) { config.getDatasourceConnectionPool().remove(datasource); } @Override public void remove(String datasource) { throw new PageRuntimeException(new DeprecatedException("method no longer supported!")); //config.getDatasourceConnectionPool().remove(datasource); } @Override public void end() { autoCommit=true; if(transConns.size()>0) { Iterator<DatasourceConnection> it = this.transConns.values().iterator(); DatasourceConnection dc; while(it.hasNext()){ dc = it.next(); try { dc.getConnection().setAutoCommit(true); } catch (SQLException e) { ExceptionHandler.printStackTrace(e); } releaseConnection(null, dc); } transConns.clear(); } this.isolation=Connection.TRANSACTION_NONE; } @Override public void release() { end(); } }