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.runtime.db;
020
021import java.sql.Connection;
022import java.sql.SQLException;
023import java.util.HashMap;
024import java.util.Iterator;
025import java.util.Map;
026import java.util.concurrent.ConcurrentHashMap;
027
028import lucee.commons.db.DBUtil;
029import lucee.commons.io.IOUtil;
030import lucee.commons.lang.ExceptionUtil;
031import lucee.commons.lang.StringUtil;
032import lucee.commons.lang.types.RefInteger;
033import lucee.commons.lang.types.RefIntegerImpl;
034import lucee.commons.lang.types.RefIntegerSync;
035import lucee.runtime.config.Config;
036import lucee.runtime.exp.DatabaseException;
037import lucee.runtime.exp.PageException;
038import lucee.runtime.op.Caster;
039import lucee.runtime.type.util.ArrayUtil;
040
041public class DatasourceConnectionPool {
042        
043        private ConcurrentHashMap<String,DCStack> dcs=new ConcurrentHashMap<String,DCStack>();
044        private Map<String,RefInteger> counter=new ConcurrentHashMap<String,RefInteger>();
045        
046        public DatasourceConnection getDatasourceConnection(DataSource datasource, String user, String pass) throws PageException {
047                // pc=ThreadLocalPageContext.get(pc);
048                if(StringUtil.isEmpty(user)) {
049            user=datasource.getUsername();
050            pass=datasource.getPassword();
051        }
052        if(pass==null)pass="";
053                
054                // get stack
055                DCStack stack=getDCStack(datasource,user,pass);
056                
057                
058                // max connection
059                int max=datasource.getConnectionLimit();
060                
061                // get an existing connection
062                DatasourceConnectionImpl rtn=null;
063                do {
064                        // we have a bad connection
065                        if(rtn!=null) {
066                                IOUtil.closeEL(rtn.getConnection());
067                        }
068                        synchronized (stack) {
069                                while(max!=-1 && max<=_size(datasource)) {
070                                        try {
071                                                //stack.inc();
072                                                stack.wait(10000L);
073                                                
074                                        } 
075                                        catch (InterruptedException e) {
076                                                throw Caster.toPageException(e);
077                                        }
078                                }
079                                
080                                while(!stack.isEmpty()) {
081                                        DatasourceConnectionImpl dc=(DatasourceConnectionImpl) stack.get();
082                                        if(dc!=null){
083                                                rtn=dc;
084                                                break;
085                                        }
086                                }
087                        }
088                }while(rtn!=null && !isValid(rtn,Boolean.TRUE));
089                
090                // create a new connection
091                if(rtn==null)
092                        rtn=loadDatasourceConnection(datasource, user, pass);
093                
094                synchronized (stack) {
095                        _inc(datasource);
096                }
097                return rtn.using();
098        }
099
100        private DatasourceConnectionImpl loadDatasourceConnection(DataSource ds, String user, String pass) throws DatabaseException  {
101        Connection conn=null;
102        String connStr = ds.getDsnTranslated();
103        try {
104                conn = DBUtil.getConnection(connStr, user, pass);
105                conn.setAutoCommit(true);
106        } 
107        catch (SQLException e) {
108                throw new DatabaseException(e,null);
109        }
110                //print.err("create connection");
111        return new DatasourceConnectionImpl(conn,ds,user,pass);
112    }
113
114        public void releaseDatasourceConnection(Config config,DatasourceConnection dc, boolean async) {
115                releaseDatasourceConnection(dc,false);
116                //if(async)((SpoolerEngineImpl)config.getSpoolerEngine()).add((DatasourceConnectionImpl)dc);
117                //else releaseDatasourceConnection(dc);
118        }
119        public void releaseDatasourceConnection(Config config,DatasourceConnection dc, boolean async, boolean closeIt) {
120                releaseDatasourceConnection(dc,closeIt);
121                //if(async)((SpoolerEngineImpl)config.getSpoolerEngine()).add((DatasourceConnectionImpl)dc);
122                //else releaseDatasourceConnection(dc);
123        }
124        
125        public void releaseDatasourceConnection(DatasourceConnection dc) {
126                releaseDatasourceConnection(dc, false);
127        }
128        
129        public void releaseDatasourceConnection(DatasourceConnection dc, boolean closeIt) {
130                if(dc==null) return;
131                DCStack stack=getDCStack(dc.getDatasource(), dc.getUsername(), dc.getPassword());
132                synchronized (stack) {
133                        if(closeIt) IOUtil.closeEL(dc.getConnection());
134                        else stack.add(dc);
135                        int max = dc.getDatasource().getConnectionLimit();
136
137                        if(max!=-1) {
138                                _dec(dc.getDatasource());
139                                stack.notify();
140                                 
141                        }
142                        else _dec(dc.getDatasource());
143                }
144        }
145
146        public void clear() {
147                //int size=0;
148                
149                // remove all timed out conns
150                try{
151                        Object[] arr = dcs.entrySet().toArray();
152                        if(ArrayUtil.isEmpty(arr)) return;
153                        for(int i=0;i<arr.length;i++) {
154                                DCStack conns=(DCStack) ((Map.Entry) arr[i]).getValue();
155                                if(conns!=null)conns.clear();
156                                //size+=conns.size();
157                        }
158                }
159                catch(Throwable t){
160                ExceptionUtil.rethrowIfNecessary(t);
161        }
162        }
163
164        public void remove(DataSource datasource) {
165                Object[] arr = dcs.keySet().toArray();
166                String key,id=createId(datasource);
167        for(int i=0;i<arr.length;i++) {
168                key=(String) arr[i];
169                if(key.startsWith(id)) {
170                                DCStack conns=dcs.get(key);
171                                conns.clear();
172                }
173                }
174        
175        RefInteger ri=counter.get(id);
176                if(ri!=null)ri.setValue(0);
177                else counter.put(id,new RefIntegerSync(0));
178        
179        }
180        
181
182        
183        public static boolean isValid(DatasourceConnection dc,Boolean autoCommit) {
184                try {
185                        if(dc.getConnection().isClosed())return false;
186                } 
187                catch (Throwable t) {
188                ExceptionUtil.rethrowIfNecessary(t);
189                return false;
190        }
191
192                try {
193                        if(dc.getDatasource().validate() && !DataSourceUtil.isValid(dc,10))return false;
194                } 
195                catch (Throwable t) {
196                ExceptionUtil.rethrowIfNecessary(t);
197        } // not all driver support this, because of that we ignore a error here, also protect from java 5
198                
199                
200                try {
201                        if(autoCommit!=null) dc.getConnection().setAutoCommit(autoCommit.booleanValue());
202                } 
203                catch (Throwable t) {
204                ExceptionUtil.rethrowIfNecessary(t);
205                return false;
206        }
207                
208                
209                return true;
210        }
211
212
213        private DCStack getDCStack(DataSource datasource, String user, String pass) {
214                String id = createId(datasource,user,pass);
215                synchronized(id) {
216                        DCStack stack=dcs.get(id);
217                
218                        if(stack==null){
219                                dcs.put(id, stack=new DCStack());
220                        }
221                        return stack;
222                }
223        }
224        
225        public int openConnections() {
226                Iterator<DCStack> it = dcs.values().iterator();
227                int count=0;
228                while(it.hasNext()){
229                        count+=it.next().openConnections();
230                }
231                return count;
232        }
233
234        private void _inc(DataSource datasource) {
235                _getCounter(datasource).plus(1);
236        }
237        private void _dec(DataSource datasource) {
238                _getCounter(datasource).minus(1);
239        }
240        private int _size(DataSource datasource) {
241                return _getCounter(datasource).toInt();
242        }
243
244        private RefInteger _getCounter(DataSource datasource) {
245                String did = createId(datasource);
246                RefInteger ri=counter.get(did);
247                if(ri==null) {
248                        counter.put(did,ri=new RefIntegerSync(0));
249                }
250                return ri;
251        }
252
253        public static String createId(DataSource datasource, String user, String pass) {
254                return createId(datasource)+":"+user+":"+pass;
255        }
256        private static String createId(DataSource datasource) {
257                if(datasource instanceof DataSourcePro) return ((DataSourceSupport)datasource).id();
258                return datasource.getClazz().getName()+":"+datasource.getDsnTranslated()+":"+datasource.getClazz().getName();
259        }
260}