001: /*
002: * Copyright 2004-2006 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.lucene.store.jdbc.datasource;
018:
019: import java.io.PrintWriter;
020: import java.lang.reflect.InvocationHandler;
021: import java.lang.reflect.InvocationTargetException;
022: import java.lang.reflect.Method;
023: import java.lang.reflect.Proxy;
024: import java.sql.Connection;
025: import java.sql.SQLException;
026: import java.util.HashMap;
027: import java.util.Map;
028: import javax.sql.DataSource;
029:
030: import org.apache.lucene.store.jdbc.index.FetchPerTransactionJdbcIndexInput;
031:
032: /**
033: * Proxy for a target DataSource, adding awareness of local managed transactions.
034: * Similar to a transactional JNDI DataSource as provided by a J2EE server.
035: * <p/>
036: * Encapsulates both a simple transaction manager based on <code>ThreadLocal</code>
037: * and a <code>DataSource</code> that supports it. Should be used when no tranasction
038: * managers are used (like JTA or Spring) in order to get simpler support for transactions.
039: * <p/>
040: * It is by no means aimed at replacing the usage of a proper transaction manager, but is provided
041: * for a simple implementation of transactions for {@link org.apache.lucene.store.jdbc.JdbcDirectory}
042: * (resulting in better performance), and integration with an existing <code>DataSource</code> code.
043: * <p/>
044: * Wraps the created Jdbc <code>Connection</code> with a {@link ConnectionProxy}, which
045: * will only close the target connection if it is controlled by it.
046: * <p/>
047: * The most outer <code>Connection</code> within the context of a thread, is the controlling
048: * connection. Each inner <code>Connection</code> that will be retrieved using this data source
049: * will return the same connection, and each call to close the connection on inner connection
050: * will be disregarded. Commiting a connection should be done only on the outer most connection.
051: * <p/>
052: * A set of simple utilities are provided in the {@link DataSourceUtils} for simpler management of
053: * the <code>DataSource</code>, and special care is taken if the <code>DataSource</code> uses the
054: * {@link ConnectionProxy} (such is the case with this data soruce). For example, the
055: * {@link DataSourceUtils#commitConnectionIfPossible(java.sql.Connection)} and
056: * {@link DataSourceUtils#rollbackConnectionIfPossible(java.sql.Connection)} will only call commit/rollback
057: * if the <code>Connection</code> was created by this data source (otherwise, in a managed environment, it
058: * will be called on the actual transaction managed, or it will be using AOP).
059: * <p/>
060: * Note, that all the code that interacts with the database within the Jdbc Store package does not
061: * commit / rollbacks the connection. It only executes it's statements, and if something goes wrong
062: * throws an exception. The responsiblity for transaction management is with the calling code, and the
063: * {@link TransactionAwareDataSourceProxy} is there to help non managed transaction management.
064: *
065: * @author kimchy
066: * @see DataSourceUtils
067: * @see org.apache.lucene.store.DirectoryTemplate
068: */
069: public class TransactionAwareDataSourceProxy implements DataSource {
070:
071: private static ThreadLocal connectionHolders = new ThreadLocal();
072:
073: private DataSource dataSource;
074:
075: /**
076: * Create the data source with the given data source to wrap.
077: */
078: public TransactionAwareDataSourceProxy(DataSource dataSource) {
079: this .dataSource = dataSource;
080: }
081:
082: /**
083: * Returns the targe data source.
084: */
085: public DataSource getTargetDataSource() {
086: return dataSource;
087: }
088:
089: public int getLoginTimeout() throws SQLException {
090: return getTargetDataSource().getLoginTimeout();
091: }
092:
093: public void setLoginTimeout(int seconds) throws SQLException {
094: getTargetDataSource().setLoginTimeout(seconds);
095: }
096:
097: public PrintWriter getLogWriter() throws SQLException {
098: return getTargetDataSource().getLogWriter();
099: }
100:
101: public void setLogWriter(PrintWriter out) throws SQLException {
102: getTargetDataSource().setLogWriter(out);
103: }
104:
105: /**
106: * Not supported.
107: */
108: public Connection getConnection(String username, String password)
109: throws SQLException {
110: Map holders = (Map) connectionHolders.get();
111: if (holders == null) {
112: holders = new HashMap();
113: connectionHolders.set(holders);
114: }
115: Connection con = (Connection) holders
116: .get(getTargetDataSource());
117: if (con == null) {
118: con = getTargetDataSource().getConnection(username,
119: password);
120: holders.put(getTargetDataSource(), con);
121: return getTransactionAwareConnectionProxy(con,
122: getTargetDataSource(), true);
123: }
124: return getTransactionAwareConnectionProxy(con,
125: getTargetDataSource(), false);
126: }
127:
128: /**
129: * Creates or returns an alreay created connection.
130: * <p/>
131: * If a connection was already created within the context of the local thread, and the <code>close</code>
132: * method was not called yet, the ori connection will be returned (and will be a "not controlled"
133: * connection, which means that any call to close will be a no op).
134: * <p/>
135: * Consider using {@link DataSourceUtils#getConnection(javax.sql.DataSource)} and
136: * {@link DataSourceUtils#releaseConnection(java.sql.Connection)} for simpler usage.
137: */
138: public Connection getConnection() throws SQLException {
139: Map holders = (Map) connectionHolders.get();
140: if (holders == null) {
141: holders = new HashMap();
142: connectionHolders.set(holders);
143: }
144: Connection con = (Connection) holders
145: .get(getTargetDataSource());
146: if (con == null) {
147: con = getTargetDataSource().getConnection();
148: holders.put(getTargetDataSource(), con);
149: return getTransactionAwareConnectionProxy(con,
150: getTargetDataSource(), true);
151: }
152: return getTransactionAwareConnectionProxy(con,
153: getTargetDataSource(), false);
154: }
155:
156: /**
157: * A simple helper that return the Jdbc <code>Connection</code> wrapped in our proxy.
158: */
159: protected Connection getTransactionAwareConnectionProxy(
160: Connection target, DataSource dataSource,
161: boolean controllsConnection) {
162: return (Connection) Proxy.newProxyInstance(
163: ConnectionProxy.class.getClassLoader(),
164: new Class[] { ConnectionProxy.class },
165: new TransactionAwareInvocationHandler(target,
166: dataSource, controllsConnection));
167: }
168:
169: public <T> T unwrap(Class<T> iface) throws SQLException {
170: try {
171: Method method = dataSource.getClass().getMethod("unwarp",
172: Class.class);
173: return (T) method.invoke(dataSource, iface);
174: } catch (Exception e) {
175: throw new SQLException("Failed to invoke unwrap "
176: + e.getMessage());
177: }
178: }
179:
180: public boolean isWrapperFor(Class<?> iface) throws SQLException {
181: try {
182: Method method = dataSource.getClass().getMethod(
183: "isWrapperFor", Class.class);
184: return (Boolean) method.invoke(dataSource, iface);
185: } catch (Exception e) {
186: throw new SQLException("Failed to invoke isWrapperFor "
187: + e.getMessage());
188: }
189: }
190:
191: /**
192: * Invocation handler that delegates close calls on JDBC Connections
193: * to to being aware of thread-bound transactions.
194: */
195: private static class TransactionAwareInvocationHandler implements
196: InvocationHandler {
197:
198: private final Connection target;
199:
200: private final DataSource dataSource;
201:
202: private final boolean controlConnection;
203:
204: public TransactionAwareInvocationHandler(Connection target,
205: DataSource dataSource, boolean controlConnection) {
206: this .target = target;
207: this .dataSource = dataSource;
208: this .controlConnection = controlConnection;
209: }
210:
211: public Object invoke(Object proxy, Method method, Object[] args)
212: throws Throwable {
213: // Invocation on ConnectionProxy interface coming in...
214:
215: if (method.getName().equals("getTargetConnection")) {
216: // Handle getTargetConnection method: return underlying Connection.
217: return this .target;
218: } else if (method.getName().equals("controlConnection")) {
219: return (controlConnection ? Boolean.TRUE
220: : Boolean.FALSE);
221: } else if (method.getName().equals("equals")) {
222: // Only consider equal when proxies are identical.
223: return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
224: } else if (method.getName().equals("hashCode")) {
225: // Use hashCode of Connection proxy.
226: return new Integer(hashCode());
227: } else if (method.getName().equals("close")) {
228: if (controlConnection) {
229: Map holders = (Map) connectionHolders.get();
230: if (holders == null
231: || !holders.containsKey(dataSource)) {
232: throw new IllegalStateException(
233: "No value for data source ["
234: + dataSource
235: + "] bound to thread ["
236: + Thread.currentThread()
237: .getName() + "]");
238: }
239: Connection transConnection = (Connection) holders
240: .remove(dataSource);
241: if (holders.isEmpty()) {
242: connectionHolders.set(null);
243: }
244: // clear transactional blobs as well
245: FetchPerTransactionJdbcIndexInput
246: .releaseBlobs(transConnection);
247: transConnection.close();
248: }
249: return null;
250: }
251:
252: // Invoke method on target Connection.
253: try {
254: return method.invoke(this .target, args);
255: } catch (InvocationTargetException ex) {
256: throw ex.getTargetException();
257: }
258: }
259: }
260:
261: }
|