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
021
022import java.sql.Connection;
023import java.sql.SQLException;
024import java.util.HashMap;
025import java.util.Iterator;
026import java.util.Map;
027
028import lucee.commons.io.IOUtil;
029import lucee.runtime.PageContext;
030import lucee.runtime.PageContextImpl;
031import lucee.runtime.config.ConfigImpl;
032import lucee.runtime.engine.ThreadLocalPageContext;
033import lucee.runtime.exp.DatabaseException;
034import lucee.runtime.exp.DeprecatedException;
035import lucee.runtime.exp.ExceptionHandler;
036import lucee.runtime.exp.PageException;
037import lucee.runtime.exp.PageRuntimeException;
038import lucee.runtime.orm.ORMDatasourceConnection;
039import lucee.runtime.orm.ORMSession;
040
041/**
042 * this class handle multible db connection, transaction and logging
043 */
044public final class DatasourceManagerImpl implements DataSourceManager {
045
046        public static final String QOQ_DATASOURCE_NAME = "_queryofquerydb";
047
048        private ConfigImpl config;
049        
050        boolean autoCommit=true;
051        private int isolation=Connection.TRANSACTION_NONE;
052        private Map<DataSource,DatasourceConnection> transConns=new HashMap<DataSource,DatasourceConnection>();
053        //private DatasourceConnection transConn;
054        
055
056        /**
057         * constructor of the class
058         * @param pc
059         */
060        public DatasourceManagerImpl(ConfigImpl c) {
061                this.config=c;
062        }
063        
064        private DatasourceConnection getTDC(DataSource ds) {
065                return transConns.get(ds);
066        }
067        
068
069        @Override
070        public DatasourceConnection getConnection(PageContext pc,String _datasource, String user, String pass) throws PageException {
071                return getConnection(pc,((PageContextImpl)pc).getDataSource(_datasource), user, pass);
072        }
073
074        @Override
075        public DatasourceConnection getConnection(PageContext pc,DataSource ds, String user, String pass) throws PageException {
076                if(autoCommit)
077                        return config.getDatasourceConnectionPool().getDatasourceConnection(ds,user,pass);
078                
079                
080                pc=ThreadLocalPageContext.get(pc);
081                DatasourceConnection newDC = ((PageContextImpl)pc)._getConnection(ds,user,pass);
082
083                // transaction
084                //if(!autoCommit) {
085            try {
086                DatasourceConnection existingDC = getTDC(ds);
087                if(existingDC==null) {
088                        newDC.getConnection().setAutoCommit(false);
089                                        
090                    if(isolation!=Connection.TRANSACTION_NONE)
091                        newDC.getConnection().setTransactionIsolation(isolation);
092                    transConns.put(ds, newDC);
093                        }
094                        else if(!existingDC.equals(newDC)) {
095                        if(QOQ_DATASOURCE_NAME.equalsIgnoreCase(ds.getName())) return newDC;
096                        releaseConnection(pc, newDC);
097                                throw new DatabaseException(
098                                                "can't use different connections to the same datasource inside a single transaction",null,null,newDC);
099                        }
100                else if(newDC.getConnection().getAutoCommit()) {
101                        newDC.getConnection().setAutoCommit(false);
102                }
103            } catch (SQLException e) {
104               ExceptionHandler.printStackTrace(e);
105            }
106                //}
107                return newDC;
108        }
109
110        public void add(PageContext pc,ORMSession session) throws PageException {
111                DataSource[] sources = session.getDataSources();
112                for(int i=0;i<sources.length;i++){
113                        _add(pc,session,sources[i]);
114                }
115                
116        }
117
118        private void _add(PageContext pc,ORMSession session, DataSource ds) throws PageException {
119                
120                // transaction
121                if(!autoCommit) {
122                        ORMDatasourceConnection newDC = new ORMDatasourceConnection(pc,session,ds);
123                
124                        try {
125                DatasourceConnection existingDC = getTDC(ds);
126                if(existingDC==null) {
127                        if(isolation!=Connection.TRANSACTION_NONE)
128                                newDC.getConnection().setTransactionIsolation(isolation);
129                    transConns.put(ds, newDC);
130                    
131                        }
132                        else if(!existingDC.equals(newDC)) {
133                                releaseConnection(pc,newDC);
134                        throw new DatabaseException(
135                                                "can't use different connections to the same datasource inside a single transaction",null,null,newDC);
136                        }
137                else if(newDC.isAutoCommit()) {
138                        newDC.setAutoCommit(false);
139                }
140            } catch (SQLException e) {
141               ExceptionHandler.printStackTrace(e);
142            }
143                }
144        }
145        
146        @Override
147        public void releaseConnection(PageContext pc,DatasourceConnection dc) {
148                if(autoCommit ) {
149                        if(pc!=null && ((PageContextImpl)pc).getStopPosition()!=null) {
150                                IOUtil.closeEL(dc.getConnection());
151                        }
152                        else
153                                config.getDatasourceConnectionPool().releaseDatasourceConnection(config,dc,false);
154                }
155        }
156
157        @Override
158        public void begin() {
159                this.autoCommit=false;
160                this.isolation=Connection.TRANSACTION_NONE;             
161        }
162        
163        @Override
164        public void begin(String isolation) {
165                this.autoCommit=false;
166        
167                if(isolation.equalsIgnoreCase("read_uncommitted"))
168                    this.isolation=Connection.TRANSACTION_READ_UNCOMMITTED;
169                else if(isolation.equalsIgnoreCase("read_committed"))
170                    this.isolation=Connection.TRANSACTION_READ_COMMITTED;
171                else if(isolation.equalsIgnoreCase("repeatable_read"))
172                    this.isolation=Connection.TRANSACTION_REPEATABLE_READ;
173                else if(isolation.equalsIgnoreCase("serializable"))
174                    this.isolation=Connection.TRANSACTION_SERIALIZABLE;
175                else 
176                    this.isolation=Connection.TRANSACTION_NONE;
177        
178        }
179    @Override
180    public void begin(int isolation) {
181        //print.out("begin:"+autoCommit);
182        this.autoCommit=false;
183        this.isolation=isolation;
184    }
185
186        @Override
187        public void rollback() throws DatabaseException {
188                if(autoCommit || transConns.size()==0)return;
189                
190                Iterator<DatasourceConnection> it = this.transConns.values().iterator();
191                DatasourceConnection dc=null;
192                try {
193                        while(it.hasNext()){
194                                dc= it.next();
195                                dc.getConnection().rollback();
196                        }
197                } 
198                catch (SQLException e) {
199                        throw new DatabaseException(e,dc);
200                }
201        }
202        
203        @Override
204        public void savepoint() throws DatabaseException {
205                if(autoCommit || transConns.size()==0)return;
206                
207                Iterator<DatasourceConnection> it = this.transConns.values().iterator();
208                DatasourceConnection dc=null;
209                try {
210                        while(it.hasNext()){
211                                dc= it.next();
212                                dc.getConnection().setSavepoint();
213                        }
214                } 
215                catch (SQLException e) {
216                        throw new DatabaseException(e,dc);
217                }
218        }
219
220        @Override
221        public void commit() throws DatabaseException {
222                if(autoCommit || transConns.size()==0)return ;
223                
224                Iterator<DatasourceConnection> it = this.transConns.values().iterator();
225                DatasourceConnection dc=null;
226                try {
227                        while(it.hasNext()){
228                                dc= it.next();
229                                dc.getConnection().commit();
230                        }
231                } 
232                catch (SQLException e) {
233                        throw new DatabaseException(e,dc);
234                }
235        }
236        
237    @Override
238    public boolean isAutoCommit() {
239        return autoCommit;
240    }
241
242    @Override
243    public void end() {
244        autoCommit=true;
245        if(transConns.size()>0) {
246                Iterator<DatasourceConnection> it = this.transConns.values().iterator();
247                DatasourceConnection dc;
248                while(it.hasNext()){
249                        dc = it.next();
250                        try {
251                        dc.getConnection().setAutoCommit(true);
252                    } 
253                    catch (SQLException e) {
254                        ExceptionHandler.printStackTrace(e);
255                    }
256                        //releaseConnection(null, dc);
257                }
258            transConns.clear();
259        }
260    }
261
262        public void remove(DataSource datasource) {
263                config.getDatasourceConnectionPool().remove(datasource);
264        }
265
266        public void remove(String datasource) {
267                throw new PageRuntimeException(new DeprecatedException("method no longer supported!"));
268                //config.getDatasourceConnectionPool().remove(datasource);
269        }
270
271        public void release() {
272                transConns.clear();
273                this.isolation=Connection.TRANSACTION_NONE;
274        }
275
276}