001: /*
002: * This software is released under a licence similar to the Apache Software Licence.
003: * See org.logicalcobwebs.proxool.package.html for details.
004: * The latest version is available at http://proxool.sourceforge.net
005: */
006: package org.logicalcobwebs.proxool;
007:
008: import org.logicalcobwebs.cglib.proxy.InvocationHandler;
009: import org.logicalcobwebs.cglib.proxy.MethodInterceptor;
010: import org.logicalcobwebs.cglib.proxy.MethodProxy;
011: import org.apache.commons.logging.Log;
012: import org.apache.commons.logging.LogFactory;
013: import org.logicalcobwebs.proxool.proxy.InvokerFacade;
014:
015: import java.lang.reflect.Method;
016: import java.lang.reflect.InvocationTargetException;
017: import java.sql.Statement;
018: import java.sql.SQLException;
019: import java.sql.Connection;
020:
021: /**
022: * Wraps up a {@link ProxyConnection}. It is proxied as a {@link java.sql.Connection}
023: * @version $Revision: 1.6 $, $Date: 2006/01/18 14:40:02 $
024: * @author <a href="mailto:bill@logicalcobwebs.co.uk">Bill Horsman</a>
025: * @author $Author: billhorsman $ (current maintainer)
026: * @since Proxool 0.9
027: */
028: public class WrappedConnection implements MethodInterceptor {
029:
030: private static final Log LOG = LogFactory
031: .getLog(WrappedConnection.class);
032:
033: private static final String CLOSE_METHOD = "close";
034:
035: private static final String IS_CLOSED_METHOD = "isClosed";
036:
037: private static final String EQUALS_METHOD = "equals";
038:
039: private static final String GET_META_DATA_METHOD = "getMetaData";
040:
041: private static final String FINALIZE_METHOD = "finalize";
042:
043: private static final String HASH_CODE_METHOD = "hashCode";
044:
045: private static final String TO_STRING_METHOD = "toString";
046:
047: /**
048: * The wrapped object. We should protect this and not expose it. We have to make sure that
049: * if we pass the proxyConnection to another WrappedConnection then this one can no longer
050: * manipulate it.
051: */
052: private ProxyConnection proxyConnection;
053:
054: private long id;
055:
056: private String alias;
057:
058: /**
059: * This gets set if the close() method is explicitly called. The {@link #getProxyConnection() proxyConnection}
060: * could still be {@link org.logicalcobwebs.proxool.ProxyConnectionIF#isReallyClosed() really closed} without
061: * this wrapper knowing about it yet.
062: */
063: private boolean manuallyClosed;
064:
065: /**
066: * Construct this wrapper around the proxy connection
067: * @param proxyConnection to wrap
068: */
069: public WrappedConnection(ProxyConnection proxyConnection) {
070: this .proxyConnection = proxyConnection;
071: this .id = proxyConnection.getId();
072: this .alias = proxyConnection.getDefinition().getAlias();
073: }
074:
075: /**
076: * Get the encapsulated proxy connection
077: * @return the proxy connection
078: */
079: public ProxyConnection getProxyConnection() {
080: return proxyConnection;
081: }
082:
083: /**
084: * Delegates to {@link #invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]) invoke}
085: * @see MethodInterceptor#intercept(java.lang.Object, java.lang.reflect.Method, java.lang.Object[], org.logicalcobwebs.cglib.proxy.MethodProxy)
086: */
087: public Object intercept(Object obj, Method method, Object[] args,
088: MethodProxy proxy) throws Throwable {
089: return invoke(proxy, method, args);
090: }
091:
092: /**
093: * Delegates all operations to the encapsulated {@link ProxyConnection} except for:
094: * <ul>
095: * <li>close()</li>
096: * <li>equals()</li>
097: * <li>hashCode()</li>
098: * <li>isClosed()</li>
099: * <li>getMetaData()</li>
100: * <li>finalize()</li>
101: * </ul>
102: * It also spots mutators and remembers that the property has been changed so that it can
103: * be {@link ConnectionResetter reset}. And any statements that are returned are remembered
104: * so that we can track whether all statements have been closed properly when the connection
105: * is returned to the pool.
106: * @see InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
107: */
108: public Object invoke(Object proxy, Method method, Object[] args)
109: throws Throwable {
110: Object result = null;
111: int argCount = args != null ? args.length : 0;
112: Method concreteMethod = method;
113: if (proxyConnection != null
114: && proxyConnection.getConnection() != null) {
115: concreteMethod = InvokerFacade.getConcreteMethod(
116: proxyConnection.getConnection().getClass(), method);
117: }
118: try {
119: if (proxyConnection != null
120: && proxyConnection.isReallyClosed()) {
121: // The user is trying to do something to this connection and it's been closed.
122: if (concreteMethod.getName().equals(IS_CLOSED_METHOD)) {
123: // That's cool. No problem checking as many times as you like.
124: } else if (concreteMethod.getName()
125: .equals(CLOSE_METHOD)) {
126: // That's cool. You can call close as often as you like.
127: } else if (manuallyClosed) {
128: // We've already manually closed this connection yet we trying to do something
129: // to it that isn't another close(). That is bad client coding :)
130: throw new SQLException(
131: "You can't perform any operations on a connection after you've called close()");
132: } else {
133: // The connection has been closed automatically. The client probably wasn't expecting
134: // that. Still, throw an exception so that they know it's all gone pear shaped.
135: throw new SQLException(
136: "You can't perform any operations on this connection. It has been automatically closed by Proxool for some reason (see logs).");
137: }
138: }
139: if (concreteMethod.getName().equals(CLOSE_METHOD)) {
140: // It's okay to close a connection twice. Only we ignore the
141: // second time.
142: if (proxyConnection != null
143: && !proxyConnection.isReallyClosed()) {
144: proxyConnection.close();
145: // Set it to null so that we can't do anything else to it.
146: proxyConnection = null;
147: manuallyClosed = true;
148: }
149: } else if (concreteMethod.getName().equals(EQUALS_METHOD)
150: && argCount == 1) {
151: result = equals(args[0]) ? Boolean.TRUE : Boolean.FALSE;
152: } else if (concreteMethod.getName()
153: .equals(HASH_CODE_METHOD)
154: && argCount == 0) {
155: result = new Integer(hashCode());
156: } else if (concreteMethod.getName()
157: .equals(IS_CLOSED_METHOD)
158: && argCount == 0) {
159: result = (proxyConnection == null || proxyConnection
160: .isClosed()) ? Boolean.TRUE : Boolean.FALSE;
161: } else if (concreteMethod.getName().equals(
162: GET_META_DATA_METHOD)
163: && argCount == 0) {
164: if (proxyConnection != null) {
165: Connection connection = ProxyFactory
166: .getWrappedConnection(proxyConnection);
167: result = ProxyFactory.getDatabaseMetaData(
168: proxyConnection.getConnection()
169: .getMetaData(), connection);
170: } else {
171: throw new SQLException(
172: "You can't perform a "
173: + concreteMethod.getName()
174: + " operation after the connection has been closed");
175: }
176: } else if (concreteMethod.getName().equals(FINALIZE_METHOD)) {
177: super .finalize();
178: } else if (concreteMethod.getName()
179: .equals(TO_STRING_METHOD)) {
180: result = toString();
181: } else {
182: if (proxyConnection != null) {
183: if (concreteMethod.getName().startsWith(
184: ConnectionResetter.MUTATOR_PREFIX)) {
185: proxyConnection.setNeedToReset(true);
186: }
187: try {
188: result = concreteMethod.invoke(proxyConnection
189: .getConnection(), args);
190: } catch (IllegalAccessException e) {
191: // This is probably because we are trying to access a non-public concrete class. But don't worry,
192: // we can always use the proxy supplied method. This will only fail if we try to use an injectable
193: // method on a method in a class that isn't public and for a method that isn't declared in an interface -
194: // but if that is the case then that method is inaccessible by any means (even by bypassing Proxool and
195: // using the vendor's driver directly).
196: LOG
197: .debug("Ignoring IllegalAccessException whilst invoking the "
198: + concreteMethod
199: + " concrete method and trying the "
200: + method + " method directly.");
201: // By overriding the method cached in the InvokerFacade we ensure that we only log this message once, and
202: // we speed up subsequent usages by not calling the method that fails first.
203: InvokerFacade.overrideConcreteMethod(
204: proxyConnection.getConnection()
205: .getClass(), method, method);
206: result = method.invoke(proxyConnection
207: .getConnection(), args);
208: }
209: } else {
210: throw new SQLException(
211: "You can't perform a "
212: + concreteMethod.getName()
213: + " operation after the connection has been closed");
214: }
215: }
216:
217: // If we have just made some sort of Statement then we should rather return
218: // a proxy instead.
219: if (result instanceof Statement) {
220: // Work out whether we were passed the sql statement during the
221: // call to get the statement object. Sometimes you do, sometimes
222: // you don't:
223: // connection.prepareCall(sql);
224: // connection.createProxyStatement();
225: String sqlStatement = null;
226: if (argCount > 0 && args[0] instanceof String) {
227: sqlStatement = (String) args[0];
228: }
229:
230: // We keep a track of all open statements
231: proxyConnection.addOpenStatement((Statement) result);
232:
233: result = ProxyFactory.getStatement((Statement) result,
234: proxyConnection.getConnectionPool(),
235: proxyConnection, sqlStatement);
236:
237: }
238:
239: } catch (InvocationTargetException e) {
240: // We might get a fatal exception here. Let's test for it.
241: if (FatalSqlExceptionHelper.testException(proxyConnection
242: .getDefinition(), e.getTargetException())) {
243: FatalSqlExceptionHelper.throwFatalSQLException(
244: proxyConnection.getDefinition()
245: .getFatalSqlExceptionWrapper(), e
246: .getTargetException());
247: }
248: throw e.getTargetException();
249: } catch (SQLException e) {
250: throw new SQLException("Couldn't perform the operation "
251: + concreteMethod.getName() + ": " + e.getMessage());
252: } catch (Exception e) {
253: LOG.error("Unexpected invocation exception", e);
254: if (FatalSqlExceptionHelper.testException(proxyConnection
255: .getDefinition(), e)) {
256: FatalSqlExceptionHelper.throwFatalSQLException(
257: proxyConnection.getDefinition()
258: .getFatalSqlExceptionWrapper(), e);
259: }
260: throw new RuntimeException(
261: "Unexpected invocation exception: "
262: + e.getMessage());
263: }
264: return result;
265: }
266:
267: /**
268: * The ID for the encapsulated {@link ProxyConnection}. This will still
269: * return the correct value after the connection is closed.
270: * @return the ID
271: */
272: public long getId() {
273: return id;
274: }
275:
276: /**
277: * Get the alias of the connection pool this connection belongs to
278: * @return {@link ConnectionPoolDefinitionIF#getAlias() alias}
279: */
280: public String getAlias() {
281: return alias;
282: }
283:
284: /**
285: * If the object passed to this method is actually a proxied version of this
286: * class then compare the real class with this one.
287: * @param obj the object to compare
288: * @return true if the object is a proxy of "this"
289: */
290: public boolean equals(Object obj) {
291: if (obj instanceof Connection) {
292: final WrappedConnection wc = ProxyFactory
293: .getWrappedConnection((Connection) obj);
294: if (wc != null && wc.getId() > 0 && getId() > 0) {
295: return wc.getId() == getId();
296: } else {
297: return false;
298: }
299: } else {
300: return false;
301: }
302: }
303:
304: /**
305: * @see Object#toString()
306: */
307: public String toString() {
308: if (proxyConnection != null) {
309: return hashCode() + "("
310: + proxyConnection.getConnection().toString() + ")";
311: } else {
312: return hashCode() + "(out of scope)";
313: }
314: }
315: }
316: /*
317: Revision history:
318: $Log: WrappedConnection.java,v $
319: Revision 1.6 2006/01/18 14:40:02 billhorsman
320: Unbundled Jakarta's Commons Logging.
321:
322: Revision 1.5 2005/10/02 12:32:58 billhorsman
323: Improve the trapping of operations after a wrapped connection is closed.
324:
325: Revision 1.4 2005/05/04 16:31:41 billhorsman
326: Use the definition referenced by the proxy connection rather than the pool instead.
327:
328: Revision 1.3 2004/07/13 21:06:21 billhorsman
329: Fix problem using injectable interfaces on methods that are declared in non-public classes.
330:
331: Revision 1.2 2004/06/02 20:50:47 billhorsman
332: Dropped obsolete InvocationHandler reference and injectable interface stuff.
333:
334: Revision 1.1 2004/03/23 21:19:45 billhorsman
335: Added disposable wrapper to proxied connection. And made proxied objects implement delegate interfaces too.
336:
337: */
|