KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mmbase > module > database > MultiConnection


1 /*
2
3 This software is OSI Certified Open Source Software.
4 OSI Certified is a certification mark of the Open Source Initiative.
5
6 The license (Mozilla version 1.0) can be read at the MMBase site.
7 See http://www.MMBase.org/license
8
9  */

10 package org.mmbase.module.database;
11
12 import java.sql.*;
13 import java.util.Map JavaDoc;
14 import java.lang.reflect.Method JavaDoc;
15
16 import org.mmbase.module.core.MMBase;
17 import org.mmbase.util.logging.Logger;
18 import org.mmbase.util.logging.Logging;
19
20 /**
21  * MultiConnection is a replacement class for Connection it provides you a
22  * multiplexed and reuseable connections from the connection pool.
23  * The main function of this class is to 'log' (keep) the last sql statement passed to it.
24  * Another function is to keep state (i.e. notifying that it is busy),
25  * and to make itself available again to teh connectionpool once it is finished (closed).
26  *
27  * @sql It would possibly be better to pass the logging of the sql query
28  * to the code that calls the conenction, rather than place it in
29  * the conenction itself, as it's implementation leads to conflicts
30  * between various JDBC versions.
31  * This also goes for freeing the connection once it is 'closed'.
32  * @author vpro
33  * @author Pierre van Rooden
34  * @version $Id: MultiConnection.java,v 1.44 2006/07/03 11:59:16 johannes Exp $
35  */

36 public class MultiConnection extends ConnectionWrapper {
37     // states
38
public final static int CON_UNUSED = 0;
39     public final static int CON_BUSY = 1;
40     public final static int CON_FINISHED = 2;
41     public final static int CON_FAILED = 3;
42
43
44     public static long queries = 0;
45     private static final Logger log = Logging.getLoggerInstance(MultiConnection.class);
46
47     /**
48      * @javadoc
49      */

50     MultiPool parent;
51     /**
52      * @javadoc
53      */

54     String JavaDoc lastSql;
55
56     private long startTimeMillis = 0;
57     private int usage = 0;
58     public int state = 0;
59
60     /**
61      * @javadoc
62      * @todo in 1.7 this method was made public,document why?
63      * @since MMBase-1.7
64      */

65     public MultiConnection(MultiPool parent,Connection con) {
66         super(con);
67         this.parent = parent;
68         state = CON_UNUSED;
69     }
70
71     /**
72      * @javadoc
73      */

74     public String JavaDoc getStateString() {
75         if (state == CON_FINISHED) {
76             return "Finished";
77         } else if (state==CON_BUSY) {
78             return "Busy";
79         } else if (state==CON_FAILED) {
80             return "Failed";
81         } else if (state==CON_UNUSED) {
82             return "Unused";
83         }
84         return "Unknown";
85     }
86
87     /**
88      * @javadoc
89      */

90     public void setLastSQL(String JavaDoc sql) {
91         lastSql = sql;
92         state = CON_BUSY;
93     }
94
95     /**
96      * @javadoc
97      */

98     public String JavaDoc getLastSQL() {
99         return lastSql;
100     }
101
102     /**
103      * createStatement returns an SQL Statement object
104      */

105     public Statement createStatement() throws SQLException {
106         MultiStatement s = new MultiStatement(this, con.createStatement());
107         return s;
108     }
109
110     /**
111      * Tries to fix the this connection, if it proves to be broken. It is supposed to be broken if
112      * the query "SELECT 1 FROM <OBJECT TABLE>" does yield an exception.
113      * This method is meant to be called in the catch after trying to use the connection.
114      *
115      * @return <code>true</code> if connection was broken and successfully repaired. <code>false</code> if connection was not broken.
116      * @throws SQLException If connection is broken and no new one could be obtained.
117      *
118      * @since MMBase-1.7.1
119      */

120
121     protected boolean checkAfterException() throws SQLException {
122         Statement s = null;
123         ResultSet rs = null;
124         try {
125             // check wether connection is still functional
126
s = createStatement();
127             rs = s.executeQuery("SELECT 1 FROM " + MMBase.getMMBase().getBuilder("object").getFullTableName() + " WHERE 1 = 0"); // if this goes wrong too it can't be the query
128
} catch (SQLException isqe) {
129              // so, connection must be broken.
130
log.service("Found broken connection, will try to fix it.");
131             // get a temporary new one for this query
132
parent.replaceConnection(this);
133             return true;
134         } finally {
135             if (s != null) s.close();
136             if (rs != null) rs.close();
137         }
138         return false;
139     }
140
141     /**
142      * {@inheritDoc}
143      *
144      * If "autoCommit" is true, then all subsequent SQL statements will
145      * be executed and committed as individual transactions. Otherwise
146      * (if "autoCommit" is false) then subsequent SQL statements will
147      * all be part of the same transaction , which must be explicitly
148      * committed with either a "commit" or "rollback" call.
149      * By default new connections are initialized with autoCommit "true".
150      */

151     public void setAutoCommit(boolean enableAutoCommit) throws SQLException {
152         try {
153             con.setAutoCommit(enableAutoCommit);
154         } catch (SQLException sqe) {
155             if (checkAfterException()) {
156                 con.setAutoCommit(enableAutoCommit);
157             } else {
158                 throw sqe;
159             }
160         }
161     }
162
163     /**
164      * @since MMBase-1.7
165      */

166     private String JavaDoc getLogSqlMessage(long time) {
167         StringBuffer JavaDoc mes = new StringBuffer JavaDoc();
168         mes.append('#');
169         mes.append(queries);
170         mes.append(" ");
171         if (time < 10) mes.append(' ');
172         if (time < 100) mes.append(' ');
173         if (time < 1000) mes.append(' ');
174         mes.append(time);
175         mes.append(" ms: ").append(getLastSQL());
176         return mes.toString();
177     }
178     /**
179      * Close connections
180      */

181     public void close() throws SQLException {
182         long time = System.currentTimeMillis() - getStartTimeMillis();
183         long maxLifeTime = parent.getMaxLifeTime();
184         if (time < maxLifeTime / 24) { // ok, you can switch on query logging with setting logging of this class on debug
185
log.debug(getLogSqlMessage(time));
186         } else if (time < maxLifeTime / 4) { // maxLifeTime / 24 (~ 5 s) is too long, but perhaps that's still ok.
187
if (log.isServiceEnabled()) {
188                 log.service(getLogSqlMessage(time));
189             }
190         } else if (time < maxLifeTime / 2) { // over maxLifeTime / 4 (~ 30 s), that too is good to know
191
log.info(getLogSqlMessage(time));
192         } else { // query took more than maxLifeTime / 2 (~ 60 s), that's worth a warning
193
log.warn(getLogSqlMessage(time));
194         }
195         if (log.isDebugEnabled()) {
196             log.trace("because", new Exception JavaDoc());
197         }
198         
199         state = CON_FINISHED;
200         // If there is a parent object, this connection belongs to a pool and should not be closed,
201
// but placed back in the pool
202
// If there is no parent, the connection belongs to a datasource (thus pooling is done by the appserver)
203
// and should be closed normally
204
if (parent != null) {
205             parent.putBack(this);
206         } else {
207             realclose();
208         }
209     }
210
211     /**
212      * Close connections
213      */

214     public void realclose() throws SQLException {
215         con.close();
216     }
217
218     /**
219      * @javadoc
220      */

221     public boolean checkSQLError(Exception JavaDoc e) {
222         log.error("JDBC CHECK ERROR=" + e.toString());
223         return true;
224     }
225
226     /**
227      * @javadoc
228      */

229     public void claim() {
230         usage++;
231         queries++;
232         startTimeMillis = System.currentTimeMillis();
233     }
234
235     /**
236      * @javadoc
237      */

238     public void release() {
239         startTimeMillis = 0;
240     }
241
242     /**
243      * @javadoc
244      */

245     public int getUsage() {
246         return usage;
247     }
248
249     /**
250      * @since MMBase-1.8
251      */

252     void resetUsage() {
253         usage = 0;
254     }
255
256     /**
257      * Returns the moment on which the last SQL statement was started in seconds after 1970.
258      */

259     public int getStartTime() {
260         return (int) (startTimeMillis / 1000);
261     }
262
263     /**
264      * Returns the moment on which the last SQL statement was started in milliseconds after 1970.
265      */

266     public long getStartTimeMillis() {
267         return startTimeMillis;
268     }
269
270
271     /**
272      * @javadoc
273      */

274     public String JavaDoc toString() {
275         return "'"+getLastSQL()+"'@"+hashCode();
276     }
277     /**
278      * createStatement returns an SQL Statement object
279      */

280     public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
281         return new MultiStatement(this, con.createStatement(resultSetType, resultSetConcurrency));
282     }
283
284
285     /**
286      * {@inheritDoc}
287      * @since MMBase 1.5, JDBC 1.4
288      */

289     public Statement createStatement(int type, int concurrency, int holdability) throws SQLException {
290         return new MultiStatement(this, con.createStatement(type, concurrency, holdability));
291     }
292
293     /**
294      * Return the underlying real connection. NOTE: use with extreme caution! MMBase is supposed to look
295      * after it's own connections. This method is public only for the reason that specific database
296      * implementations need access to this connection in order to safely clear them before they
297      * can be put back in the connection pool.
298      */

299     public Connection getRealConnection() {
300         return con;
301     }
302 }
303
304
305
Popular Tags