/** * 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 java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import lucee.commons.io.IOUtil; import lucee.commons.io.SystemUtil; import lucee.commons.lang.ExceptionUtil; import lucee.commons.lang.StringUtil; import lucee.commons.lang.types.RefInteger; import lucee.runtime.config.Config; import lucee.runtime.engine.ThreadLocalPageContext; import lucee.runtime.exp.DatabaseException; import lucee.runtime.exp.PageException; import lucee.runtime.op.Caster; import lucee.runtime.type.Struct; import lucee.runtime.type.StructImpl; import lucee.runtime.type.util.ArrayUtil; import lucee.runtime.type.util.KeyConstants; public class DatasourceConnectionPool { private static final long WAIT = 1000L; private final Object waiter=new Object(); private ConcurrentHashMap<String,DCStack> dcs=new ConcurrentHashMap<String,DCStack>(); // !!! do not change used in hibernate extension public DatasourceConnection getDatasourceConnection(Config config,DataSource datasource, String user, String pass) throws PageException { config=ThreadLocalPageContext.getConfig(config); if(StringUtil.isEmpty(user)) { user=datasource.getUsername(); pass=datasource.getPassword(); } if(pass==null)pass=""; // get stack DCStack stack=getDCStack(datasource,user,pass); int max=datasource.getConnectionLimit(); // get an existing connection DatasourceConnection rtn; boolean wait=false; outer:while(true) { rtn=null; // wait until it is again my turn if(wait) { SystemUtil.wait(waiter,WAIT); wait=false; } synchronized (stack) { // do we have already to many open connections? if(max!=-1) { RefInteger _counter = stack.getCounter();//_getCounter(stack,datasource,user,pass); if(max<=_counter.toInt()) {// go back ant wait wait=true; continue outer; } } // get an existing connection while(!stack.isEmpty()) { DatasourceConnection dc=(DatasourceConnection) stack.get(); if(dc!=null){ rtn=dc; break; } } _inc(stack,datasource,user,pass); // if new or fine we increase in any case // create a new instance if(rtn==null) { rtn=loadDatasourceConnection(config,datasource, user, pass); if(rtn instanceof DatasourceConnectionImpl) ((DatasourceConnectionImpl)rtn).using(); return rtn; } } // we get us a fine connection (we do validation outside the synchronized to safe shared time) if(isValid(rtn,Boolean.TRUE)) { if(rtn instanceof DatasourceConnectionImpl) ((DatasourceConnectionImpl)rtn).using(); return rtn; } // we have an invalid connection (above check failed), so we have to start over synchronized (stack) { _dec(stack,datasource,user,pass); // we already did increment in case we are fine SystemUtil.notify(waiter); } IOUtil.closeEL(rtn.getConnection()); rtn=null; } } private DatasourceConnectionImpl loadDatasourceConnection(Config config,DataSource ds, String user, String pass) throws PageException { Connection conn=null; try { conn = ds.getConnection(config, user, pass); conn.setAutoCommit(true); } catch (SQLException e) { throw new DatabaseException(e,null); } catch (Exception e) { throw Caster.toPageException(e); } return new DatasourceConnectionImpl(conn,ds,user,pass); } public void releaseDatasourceConnection(DatasourceConnection dc,boolean closeIt) { if(dc==null) return; DCStack stack=getDCStack(dc.getDatasource(), dc.getUsername(), dc.getPassword()); synchronized (stack) { if(closeIt) IOUtil.closeEL(dc.getConnection()); else stack.add(dc); int max =dc.getDatasource().getConnectionLimit(); if(max!=-1) { _dec(stack,dc.getDatasource(),dc.getUsername(),dc.getPassword()); SystemUtil.notify(waiter); } else _dec(stack,dc.getDatasource(),dc.getUsername(),dc.getPassword()); } } public void releaseDatasourceConnection(DatasourceConnection dc) { releaseDatasourceConnection(dc, false); } public void clear(boolean force) { // remove all timed out conns try{ Object[] arr = dcs.entrySet().toArray(); if(ArrayUtil.isEmpty(arr)) return; for(int i=0;i<arr.length;i++) { DCStack conns=(DCStack) ((Map.Entry) arr[i]).getValue(); if(conns!=null)conns.clear(force); } } catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} } public void clear(String dataSourceName,boolean force) { // remove all timed out conns try{ Object[] arr = dcs.entrySet().toArray(); if(ArrayUtil.isEmpty(arr)) return; Entry e; for(int i=0;i<arr.length;i++) { e = ((Map.Entry) arr[i]); DCStack conns=(DCStack) e.getValue(); DatasourceConnection dc = conns.get(); if(dc!=null) { String name=dc.getDatasource().getName(); if(dataSourceName.equalsIgnoreCase(name)) { if(conns!=null)conns.clear(force); } } } } catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} } public void remove(DataSource datasource) { if(datasource==null) return; // remove existing connections { Iterator<Entry<String, DCStack>> it = dcs.entrySet().iterator(); Entry<String, DCStack> e; while(it.hasNext()) { e = it.next(); if(datasource.equals(e.getValue().getDatasource())) { e.getValue().clear(true); } } } } public static boolean isValid(DatasourceConnection dc,Boolean autoCommit) { try { if(dc.getConnection().isClosed())return false; } catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);return false;} try { if(dc.getDatasource().validate() && !DataSourceUtil.isValid(dc,1000))return false; } catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} // not all driver support this, because of that we ignore a error here, also protect from java 5 try { if(autoCommit!=null) dc.getConnection().setAutoCommit(autoCommit.booleanValue()); } catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);return false;} return true; } public Struct meta() { Iterator<Entry<String, DCStack>> it = dcs.entrySet().iterator(); Entry<String, DCStack> e; DCStack dcstack; DataSource ds; Struct sct; Struct arr=new StructImpl(); while(it.hasNext()) { e = it.next(); dcstack = e.getValue(); ds = dcstack.getDatasource(); sct=new StructImpl(); try {sct.setEL(KeyConstants._name, ds.getName());}catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} try {sct.setEL("connectionLimit", ds.getConnectionLimit());}catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} try {sct.setEL("connectionTimeout", ds.getConnectionTimeout());}catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} try {sct.setEL("connectionString", ds.getConnectionStringTranslated());}catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} try {sct.setEL("openConnections", openConnections(ds.getName()));}catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} try {sct.setEL(KeyConstants._database, ds.getDatabase());}catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} if(sct.size()>0)arr.setEL(ds.getName(),sct); } return arr; } private DCStack getDCStack(DataSource datasource, String user, String pass) { String id = createId(datasource,user,pass); synchronized(id) { DCStack stack=dcs.get(id); if(stack==null){ dcs.put(id, stack=new DCStack(datasource,user,pass)); } return stack; } } public Map<String,Integer> openConnections() { Map<String,Integer> map=new HashMap<String, Integer>(); Iterator<DCStack> it = dcs.values().iterator(); // all connections in pool DCStack dcstack; while(it.hasNext()) { dcstack=it.next(); Integer val = map.get(dcstack.getDatasource().getName()); if(val==null) val=dcstack.openConnections(); else val=val.intValue()+dcstack.openConnections(); map.put(dcstack.getDatasource().getName(), val); } return map; } public int openConnections(String dataSourceName) { Integer res = openConnections().get(dataSourceName); if(res==null) return -1; return res.intValue(); } private void _inc(DCStack stack, DataSource datasource, String username,String password) { RefInteger c = stack.getCounter(); c.plus(1); } private void _dec(DCStack stack, DataSource datasource, String username,String password) { RefInteger c = stack.getCounter(); c.minus(1); } public static String createId(DataSource datasource, String user, String pass) { return datasource.id()+"::"+user+":"+pass; } }