001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010: package org.mmbase.module.database;
011:
012: import java.sql.*;
013:
014: import org.mmbase.module.core.MMBase;
015: import org.mmbase.util.logging.Logger;
016: import org.mmbase.util.logging.Logging;
017:
018: /**
019: * MultiConnection is a replacement class for Connection it provides you a
020: * multiplexed and reuseable connections from the connection pool.
021: * The main function of this class is to 'log' (keep) the last sql statement passed to it.
022: * Another function is to keep state (i.e. notifying that it is busy),
023: * and to make itself available again to teh connectionpool once it is finished (closed).
024: *
025: * @sql It would possibly be better to pass the logging of the sql query
026: * to the code that calls the conenction, rather than place it in
027: * the conenction itself, as it's implementation leads to conflicts
028: * between various JDBC versions.
029: * This also goes for freeing the connection once it is 'closed'.
030: * @author vpro
031: * @author Pierre van Rooden
032: * @version $Id: MultiConnectionImplementation.java,v 1.2 2007/12/06 08:05:51 michiel Exp $
033: * @since MMBase-1.9 (as 'MultiConnection' in < 1.9)
034: */
035: public class MultiConnectionImplementation extends ConnectionWrapper
036: implements MultiConnection {
037: // states
038: public final static int CON_UNUSED = 0;
039: public final static int CON_BUSY = 1;
040: public final static int CON_FINISHED = 2;
041: public final static int CON_FAILED = 3;
042:
043: public static long queries = 0;
044:
045: private static final Logger log = Logging
046: .getLoggerInstance(MultiConnection.class);
047:
048: /**
049: * @javadoc
050: */
051: MultiPool parent;
052: /**
053: * @javadoc
054: */
055: String lastSql;
056:
057: Exception stackTrace;
058:
059: private long startTimeMillis = 0;
060: private int usage = 0;
061: public int state = 0;
062:
063: /**
064: * @javadoc
065: * @todo in 1.7 this method was made public,document why?
066: * @since MMBase-1.7
067: */
068: public MultiConnectionImplementation(MultiPool parent,
069: Connection con) {
070: super (con);
071: this .parent = parent;
072: state = CON_UNUSED;
073: }
074:
075: public MultiPool getParent() {
076: return parent;
077: }
078:
079: /**
080: * @javadoc
081: */
082: public String getStateString() {
083: if (state == CON_FINISHED) {
084: return "Finished";
085: } else if (state == CON_BUSY) {
086: return "Busy";
087: } else if (state == CON_FAILED) {
088: return "Failed";
089: } else if (state == CON_UNUSED) {
090: return "Unused";
091: }
092: return "Unknown";
093: }
094:
095: /**
096: * @javadoc
097: */
098: public void setLastSQL(String sql) {
099: lastSql = sql;
100: state = CON_BUSY;
101: }
102:
103: /**
104: * @javadoc
105: */
106: public String getLastSQL() {
107: return lastSql;
108: }
109:
110: public Exception getStackTrace() {
111: return stackTrace;
112: }
113:
114: /**
115: * createStatement returns an SQL Statement object
116: */
117: public Statement createStatement() throws SQLException {
118: MultiStatement s = new MultiStatement(this , con
119: .createStatement());
120: return s;
121: }
122:
123: /**
124: * Tries to fix the this connection, if it proves to be broken. It is supposed to be broken if
125: * the query "SELECT 1 FROM <OBJECT TABLE>" does yield an exception.
126: * This method is meant to be called in the catch after trying to use the connection.
127: *
128: * @return <code>true</code> if connection was broken and successfully repaired. <code>false</code> if connection was not broken.
129: * @throws SQLException If connection is broken and no new one could be obtained.
130: *
131: * @since MMBase-1.7.1
132: */
133:
134: public boolean checkAfterException() throws SQLException {
135: Statement s = null;
136: ResultSet rs = null;
137: try {
138: // check wether connection is still functional
139: s = createStatement();
140: rs = s.executeQuery("SELECT 1 FROM "
141: + MMBase.getMMBase().getBuilder("object")
142: .getFullTableName() + " WHERE 1 = 0"); // if this goes wrong too it can't be the query
143: } catch (SQLException isqe) {
144: // so, connection must be broken.
145: log.service("Found broken connection, will try to fix it.");
146: // get a temporary new one for this query
147: parent.replaceConnection(this );
148: return true;
149: } finally {
150: if (s != null)
151: s.close();
152: if (rs != null)
153: rs.close();
154: }
155: return false;
156: }
157:
158: /**
159: * {@inheritDoc}
160: *
161: * If "autoCommit" is true, then all subsequent SQL statements will
162: * be executed and committed as individual transactions. Otherwise
163: * (if "autoCommit" is false) then subsequent SQL statements will
164: * all be part of the same transaction , which must be explicitly
165: * committed with either a "commit" or "rollback" call.
166: * By default new connections are initialized with autoCommit "true".
167: */
168: public void setAutoCommit(boolean enableAutoCommit)
169: throws SQLException {
170: try {
171: con.setAutoCommit(enableAutoCommit);
172: } catch (SQLException sqe) {
173: if (checkAfterException()) {
174: con.setAutoCommit(enableAutoCommit);
175: } else {
176: throw sqe;
177: }
178: }
179: }
180:
181: /**
182: * @since MMBase-1.7
183: */
184: private String getLogSqlMessage(long time) {
185: StringBuffer mes = new StringBuffer();
186: mes.append('#');
187: mes.append(queries);
188: mes.append(" ");
189: if (time < 10)
190: mes.append(' ');
191: if (time < 100)
192: mes.append(' ');
193: if (time < 1000)
194: mes.append(' ');
195: mes.append(time);
196: mes.append(" ms: ").append(getLastSQL());
197: return mes.toString();
198: }
199:
200: /**
201: * Close connections
202: */
203: public void close() throws SQLException {
204: long time = System.currentTimeMillis() - getStartTimeMillis();
205: long maxLifeTime = parent.getMaxLifeTime();
206: if (time < maxLifeTime / 24) { // ok, you can switch on query logging with setting logging of this class on debug
207: log.debug(getLogSqlMessage(time));
208: } else if (time < maxLifeTime / 4) { // maxLifeTime / 24 (~ 5 s) is too long, but perhaps that's still ok.
209: if (log.isServiceEnabled()) {
210: log.service(getLogSqlMessage(time));
211: }
212: } else if (time < maxLifeTime / 2) { // over maxLifeTime / 4 (~ 30 s), that too is good to know
213: log.info(getLogSqlMessage(time));
214: } else { // query took more than maxLifeTime / 2 (~ 60 s), that's worth a warning
215: log.warn(getLogSqlMessage(time));
216: }
217: if (log.isDebugEnabled()) {
218: log.trace("because", new Exception());
219: }
220:
221: state = CON_FINISHED;
222: // If there is a parent object, this connection belongs to a pool and should not be closed,
223: // but placed back in the pool
224: // If there is no parent, the connection belongs to a datasource (thus pooling is done by the appserver)
225: // and should be closed normally
226: if (parent != null) {
227: parent.putBack(this );
228: } else {
229: realclose();
230: }
231: }
232:
233: /**
234: * Close connections
235: */
236: public void realclose() throws SQLException {
237: con.close();
238: }
239:
240: /**
241: * @javadoc
242: */
243: public boolean checkSQLError(Exception e) {
244: log.error("JDBC CHECK ERROR=" + e.toString());
245: return true;
246: }
247:
248: /**
249: * @javadoc
250: */
251: public void claim() {
252: usage++;
253: queries++;
254: startTimeMillis = System.currentTimeMillis();
255: if (log.isDebugEnabled()) {
256: stackTrace = new Exception();
257: }
258: }
259:
260: /**
261: * @javadoc
262: */
263: public void release() {
264: startTimeMillis = 0;
265: stackTrace = null;
266: }
267:
268: /**
269: * @javadoc
270: */
271: public int getUsage() {
272: return usage;
273: }
274:
275: /**
276: * @since MMBase-1.8
277: */
278: public void resetUsage() {
279: usage = 0;
280: }
281:
282: /**
283: * Returns the moment on which the last SQL statement was started in seconds after 1970.
284: */
285: public int getStartTime() {
286: return (int) (startTimeMillis / 1000);
287: }
288:
289: /**
290: * Returns the moment on which the last SQL statement was started in milliseconds after 1970.
291: */
292: public long getStartTimeMillis() {
293: return startTimeMillis;
294: }
295:
296: /**
297: * @javadoc
298: */
299: public String toString() {
300: return "'" + getLastSQL() + "'@" + hashCode();
301: }
302:
303: /**
304: * createStatement returns an SQL Statement object
305: */
306: public Statement createStatement(int resultSetType,
307: int resultSetConcurrency) throws SQLException {
308: return new MultiStatement(this , con.createStatement(
309: resultSetType, resultSetConcurrency));
310: }
311:
312: /**
313: * {@inheritDoc}
314: * @since MMBase 1.5, JDBC 1.4
315: */
316: public Statement createStatement(int type, int concurrency,
317: int holdability) throws SQLException {
318: return new MultiStatement(this , con.createStatement(type,
319: concurrency, holdability));
320: }
321:
322: /**
323: * Return the underlying real connection. NOTE: use with extreme caution! MMBase is supposed to look
324: * after it's own connections. This method is public only for the reason that specific database
325: * implementations need access to this connection in order to safely clear them before they
326: * can be put back in the connection pool.
327: * @deprecated Use {@link #unwrap(Connection.class)} (a java 1.6 method from 'Wrapper')
328: */
329: public Connection getRealConnection() {
330: return con;
331: }
332:
333: public void wrap(Connection con) {
334: this.con = con;
335: }
336: }
|