001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.resource.adapter.jdbc.remote;
023:
024: import java.io.ByteArrayOutputStream;
025: import java.io.IOException;
026: import java.io.InputStream;
027: import java.io.ObjectOutputStream;
028: import java.io.Serializable;
029: import java.lang.reflect.InvocationTargetException;
030: import java.lang.reflect.Method;
031: import java.lang.reflect.UndeclaredThrowableException;
032: import java.sql.CallableStatement;
033: import java.sql.Connection;
034: import java.sql.DatabaseMetaData;
035: import java.sql.ParameterMetaData;
036: import java.sql.PreparedStatement;
037: import java.sql.ResultSet;
038: import java.sql.ResultSetMetaData;
039: import java.sql.SQLException;
040: import java.sql.Statement;
041: import java.util.ArrayList;
042: import java.util.HashMap;
043: import java.util.Iterator;
044: import java.util.Map;
045:
046: import javax.management.ObjectName;
047: import javax.naming.BinaryRefAddr;
048: import javax.naming.InitialContext;
049: import javax.naming.NamingException;
050: import javax.naming.Reference;
051: import javax.naming.StringRefAddr;
052: import javax.resource.Referenceable;
053: import javax.sql.DataSource;
054:
055: import org.jboss.deployment.DeploymentException;
056: import org.jboss.invocation.Invocation;
057: import org.jboss.invocation.Invoker;
058: import org.jboss.invocation.InvokerInterceptor;
059: import org.jboss.invocation.MarshalledInvocation;
060: import org.jboss.logging.Logger;
061: import org.jboss.proxy.ClientMethodInterceptor;
062: import org.jboss.proxy.GenericProxyFactory;
063: import org.jboss.resource.connectionmanager.ConnectionFactoryBindingService;
064: import org.jboss.system.Registry;
065: import org.jboss.util.naming.NonSerializableFactory;
066: import org.jboss.util.naming.Util;
067: import org.jboss.util.Classes;
068:
069: /**
070: * An mbean service that pvovides the detached invoker ops for the
071: * javax.sql.DataSource and related java.sql.* interfaces.
072: *
073: * TODO this does not belong in the resource adapter
074: * @author Scott.Stark@jboss.org
075: * @author Tom.Elrod@jboss.org
076: * @author adrian@jboss.com
077: * @version $Revision: 57189 $
078: */
079: public class WrapperDataSourceService extends
080: ConnectionFactoryBindingService implements
081: WrapperDataSourceServiceMBean {
082: private static Logger log = Logger
083: .getLogger(WrapperDataSourceService.class);
084:
085: private ObjectName jmxInvokerName;
086: private Invoker delegateInvoker;
087: private Object theProxy;
088: private HashMap marshalledInvocationMapping = new HashMap();
089: private HashMap connectionMap = new HashMap();
090: private HashMap statementMap = new HashMap();
091: private HashMap resultSetMap = new HashMap();
092: private HashMap lobMap = new HashMap();
093: private HashMap databaseMetaDataMap = new HashMap();
094: private boolean trace = log.isTraceEnabled();
095:
096: protected void startService() throws Exception {
097: determineBindName();
098: createConnectionFactory();
099: if (jmxInvokerName != null) {
100: createProxy();
101: calculateMethodHases();
102: bindConnectionFactory();
103: } else {
104: super .bindConnectionFactory();
105: }
106: }
107:
108: protected void stopService() throws Exception {
109: unbindConnectionFactory();
110: if (jmxInvokerName != null)
111: destroyProxy();
112: }
113:
114: protected void bindConnectionFactory() throws Exception {
115: InitialContext ctx = new InitialContext();
116: try {
117: log.debug("Binding object '" + cf + "' into JNDI at '"
118: + bindName + "'");
119: // Associated the local cf with the NonSerializable factory
120: NonSerializableFactory.rebind(bindName, cf);
121: /* Create a reference that uses the the DataSourceFactory as the
122: reference factory class. This class detects whether the lookup
123: is being done locally or remotely and returns either the just bound
124: connection factory, or a DataSource proxy that uses the detached
125: invoker framework to expose remote proxies to the server side
126: DataSource and related elements.
127: */
128: Referenceable referenceable = (Referenceable) cf;
129: // Set the DataSource proxy as the ProxyData ref address
130: ByteArrayOutputStream baos = new ByteArrayOutputStream();
131: ObjectOutputStream oos = new ObjectOutputStream(baos);
132: oos.writeObject(theProxy);
133: oos.close();
134: byte[] proxyBytes = baos.toByteArray();
135: BinaryRefAddr dsAddr = new BinaryRefAddr("ProxyData",
136: proxyBytes);
137: String factory = DataSourceFactory.class.getName();
138: Reference dsRef = new Reference("javax.sql.DataSource",
139: dsAddr, factory, null);
140: referenceable.setReference(dsRef);
141: // Set the VMID as the address local/remote indicator
142: baos.reset();
143: ObjectOutputStream oos2 = new ObjectOutputStream(baos);
144: oos2.writeObject(DataSourceFactory.vmID);
145: oos2.close();
146: byte[] id = baos.toByteArray();
147: BinaryRefAddr localAddr = new BinaryRefAddr("VMID", id);
148: dsRef.add(localAddr);
149: /* Bind the Referenceable connection factory into JNDI and set the
150: JndiName value of the reference address for use by the DataSourceFactory
151: when looking up the local factory from the NonSerializableFactory.
152: */
153: StringRefAddr jndiRef = new StringRefAddr("JndiName",
154: bindName);
155: dsRef.add(jndiRef);
156: Util.rebind(ctx, bindName, cf);
157: log.info("Bound ConnectionManager '" + serviceName
158: + "' to JNDI name '" + bindName + "'");
159: } catch (NamingException ne) {
160: throw new DeploymentException(
161: "Could not bind ConnectionFactory into jndi: "
162: + bindName, ne);
163: } finally {
164: ctx.close();
165: }
166: }
167:
168: public ObjectName getJMXInvokerName() {
169: return jmxInvokerName;
170: }
171:
172: public void setJMXInvokerName(ObjectName jmxInvokerName) {
173: this .jmxInvokerName = jmxInvokerName;
174: }
175:
176: public Object invoke(Invocation invocation) throws Exception {
177: // Set the method hash to Method mapping
178: if (invocation instanceof MarshalledInvocation) {
179: MarshalledInvocation mi = (MarshalledInvocation) invocation;
180: mi.setMethodMap(marshalledInvocationMapping);
181: }
182: // Invoke the Naming method via reflection
183: Method method = invocation.getMethod();
184: Class methodClass = method.getDeclaringClass();
185: Object[] args = invocation.getArguments();
186: Object value = null;
187:
188: try {
189: if (methodClass.isAssignableFrom(DataSource.class)) {
190: InitialContext ctx = new InitialContext();
191: DataSource ds = (DataSource) ctx.lookup(bindName);
192: value = doDataSourceMethod(ds, method, args);
193: } else if (methodClass.isAssignableFrom(Connection.class)) {
194: Integer id = (Integer) invocation.getId();
195: Connection conn = (Connection) connectionMap.get(id);
196: if (conn == null) {
197: throw new IllegalAccessException(
198: "Failed to find connection: " + id);
199: }
200: value = doConnectionMethod(conn, method, args);
201: } else if (methodClass.isAssignableFrom(Statement.class)
202: || methodClass
203: .isAssignableFrom(PreparedStatement.class)
204: || methodClass
205: .isAssignableFrom(CallableStatement.class)) {
206: Integer id = (Integer) invocation.getId();
207: Statement stmt = (Statement) statementMap.get(id);
208: if (stmt == null) {
209: throw new SQLException("Failed to find Statement: "
210: + id);
211: }
212: value = doStatementMethod(stmt, method, args);
213: } else if (methodClass.isAssignableFrom(ResultSet.class)) {
214: Integer id = (Integer) invocation.getId();
215: ResultSet results = (ResultSet) resultSetMap.get(id);
216: if (results == null) {
217: throw new IllegalAccessException(
218: "Failed to find ResultSet: " + id);
219: }
220: value = doResultSetMethod(results, method, args);
221: } else if (methodClass
222: .isAssignableFrom(DatabaseMetaData.class)) {
223: Integer id = (Integer) invocation.getId();
224: DatabaseMetaData dbMetaData = (DatabaseMetaData) databaseMetaDataMap
225: .get(id);
226: if (dbMetaData == null) {
227: throw new IllegalAccessException(
228: "Failed to find DatabaseMetaData: " + id);
229: }
230: value = doDatabaseMetaDataMethod(dbMetaData, method,
231: args);
232: } else {
233: throw new UnsupportedOperationException(
234: "Do not know how to handle method=" + method);
235: }
236: } catch (InvocationTargetException e) {
237: Throwable t = e.getTargetException();
238: if (t instanceof Exception)
239: throw (Exception) t;
240: else
241: throw new UndeclaredThrowableException(t, method
242: .toString());
243: }
244:
245: return value;
246: }
247:
248: /**
249: * Create the proxy
250: *
251: * TODO this should be external configuration
252: */
253: protected void createProxy() throws Exception {
254: /* Create an JRMPInvokerProxy that will be associated with a naming JMX
255: invoker given by the jmxInvokerName.
256: */
257: delegateInvoker = (Invoker) Registry.lookup(jmxInvokerName);
258: log.debug("Using delegate: " + delegateInvoker
259: + " for invoker=" + jmxInvokerName);
260: ObjectName targetName = getServiceName();
261: Integer nameHash = new Integer(targetName.hashCode());
262: Registry.bind(nameHash, targetName);
263:
264: Object cacheID = null;
265: String proxyBindingName = null;
266: String jndiName = null;
267: Class[] ifaces = { javax.sql.DataSource.class };
268: /* Initialize interceptorClasses with default client interceptor list
269: if no client interceptor configuration was provided */
270: ArrayList interceptorClasses = new ArrayList();
271: interceptorClasses.add(ClientMethodInterceptor.class);
272: interceptorClasses.add(InvokerInterceptor.class);
273: ClassLoader loader = Thread.currentThread()
274: .getContextClassLoader();
275: GenericProxyFactory proxyFactory = new GenericProxyFactory();
276: theProxy = proxyFactory.createProxy(cacheID, targetName,
277: delegateInvoker, jndiName, proxyBindingName,
278: interceptorClasses, loader, ifaces);
279: log.debug("Created proxy for invoker=" + jmxInvokerName
280: + ", targetName=" + targetName + ", nameHash="
281: + nameHash);
282: }
283:
284: /**
285: * Destroy the proxy
286: */
287: protected void destroyProxy() throws Exception {
288: ObjectName name = getServiceName();
289: Integer nameHash = new Integer(name.hashCode());
290: Registry.unbind(nameHash);
291: }
292:
293: /**
294: * Calculate the method hashes
295: */
296: protected void calculateMethodHases() throws Exception {
297: Method[] methods = DataSource.class.getMethods();
298: for (int m = 0; m < methods.length; m++) {
299: Method method = methods[m];
300: Long hash = new Long(MarshalledInvocation
301: .calculateHash(method));
302: marshalledInvocationMapping.put(hash, method);
303: }
304:
305: // Get the Long to Method mappings
306: Map m = MarshalledInvocation
307: .methodToHashesMap(Connection.class);
308: displayHashes(m);
309: marshalledInvocationMapping.putAll(m);
310: m = MarshalledInvocation.methodToHashesMap(Statement.class);
311: displayHashes(m);
312: marshalledInvocationMapping.putAll(m);
313: m = MarshalledInvocation
314: .methodToHashesMap(CallableStatement.class);
315: displayHashes(m);
316: marshalledInvocationMapping.putAll(m);
317: m = MarshalledInvocation
318: .methodToHashesMap(PreparedStatement.class);
319: displayHashes(m);
320: marshalledInvocationMapping.putAll(m);
321: m = MarshalledInvocation.methodToHashesMap(ResultSet.class);
322: displayHashes(m);
323: marshalledInvocationMapping.putAll(m);
324: m = MarshalledInvocation
325: .methodToHashesMap(DatabaseMetaData.class);
326: displayHashes(m);
327: marshalledInvocationMapping.putAll(m);
328: }
329:
330: private Object doDataSourceMethod(DataSource ds, Method method,
331: Object[] args) throws InvocationTargetException,
332: IllegalAccessException {
333: Object value = method.invoke(ds, args);
334: if (value instanceof Connection) {
335: value = createConnectionProxy(value);
336: } else if (value != null
337: && (value instanceof Serializable) == false) {
338: throw new IllegalAccessException("Method=" + method
339: + " does not return Serializable");
340: }
341: return value;
342: }
343:
344: private Object doConnectionMethod(Connection conn, Method method,
345: Object[] args) throws InvocationTargetException,
346: IllegalAccessException, SQLException {
347: if (trace) {
348: log.trace("doConnectionMethod, conn=" + conn + ", method="
349: + method);
350: }
351: Object value = method.invoke(conn, args);
352: if (value instanceof Statement) {
353: value = createStatementProxy(value);
354: } else if (value instanceof DatabaseMetaData) {
355: value = createDatabaseMetaData(value);
356: } else if (value != null
357: && (value instanceof Serializable) == false) {
358: throw new IllegalAccessException("Method=" + method
359: + " does not return Serializable");
360: }
361: return value;
362: }
363:
364: private Object doStatementMethod(Statement stmt, Method method,
365: Object[] args) throws InvocationTargetException,
366: IllegalAccessException, SQLException {
367: if (trace) {
368: log.trace("doStatementMethod, conn=" + stmt + ", method="
369: + method);
370: }
371:
372: if (method.getName().equals("close")) {
373: Integer id = new Integer(stmt.hashCode());
374: statementMap.remove(id);
375: log.debug("Closed Statement=" + id);
376: }
377:
378: Object value = method.invoke(stmt, args);
379: if (value instanceof ResultSet) {
380: value = createResultSetProxy(value);
381: } else if (value instanceof ResultSetMetaData) {
382: ResultSetMetaData rmd = (ResultSetMetaData) value;
383: value = new SerializableResultSetMetaData(rmd);
384: } else if (value instanceof ParameterMetaData) {
385: ParameterMetaData pmd = (ParameterMetaData) value;
386: value = new SerializableParameterMetaData(pmd);
387: } else if (value != null
388: && (value instanceof Serializable) == false) {
389: throw new IllegalAccessException("Method=" + method
390: + " does not return Serializable");
391: }
392: return value;
393: }
394:
395: private Object doResultSetMethod(ResultSet results, Method method,
396: Object[] args) throws InvocationTargetException,
397: IllegalAccessException, SQLException, IOException {
398: if (trace) {
399: log.trace("doStatementMethod, results=" + results
400: + ", method=" + method);
401: }
402:
403: if (method.getName().equals("close")) {
404: Integer id = new Integer(results.hashCode());
405: resultSetMap.remove(id);
406: log.debug("Closed ResultSet=" + id);
407: }
408:
409: Object value = method.invoke(results, args);
410: if (value instanceof ResultSetMetaData) {
411: ResultSetMetaData rmd = (ResultSetMetaData) value;
412: value = new SerializableResultSetMetaData(rmd);
413: }
414: // Need to create serializable version of ascii stream returned by result set
415: if (("getBinaryStream".equals(method.getName()) || "getAsciiStream"
416: .equals(method.getName()))
417: && value instanceof InputStream) {
418: InputStream ins = (InputStream) value;
419: value = new SerializableInputStream(ins);
420: } else if ("getCharacterStream".equals(method.getName())
421: && value instanceof java.io.Reader) {
422: java.io.Reader ins = (java.io.Reader) value;
423: value = new SerializableReader(ins);
424: } else if ("getClob".equals(method.getName())
425: || "getBlob".equals(method.getName())) {
426: value = createLobProxy(value);
427: }
428:
429: if (value != null && (value instanceof Serializable) == false) {
430: throw new IllegalAccessException("Method=" + method
431: + " does not return Serializable");
432: }
433: return value;
434: }
435:
436: private Object doDatabaseMetaDataMethod(
437: DatabaseMetaData dbMetaData, Method method, Object[] args)
438: throws InvocationTargetException, IllegalAccessException {
439: if (trace) {
440: log.trace("doDatabaseMetaDataMethod, dbMetaData="
441: + dbMetaData + ", method=" + method);
442: }
443:
444: Object value = method.invoke(dbMetaData, args);
445: if (value instanceof ResultSet) {
446: value = createResultSetProxy(value);
447: } else if (value instanceof Connection) {
448: value = createConnectionProxy(value);
449: }
450: if (value != null && (value instanceof Serializable) == false) {
451: throw new IllegalAccessException("Method=" + method
452: + " does not return Serializable");
453: }
454: return value;
455: }
456:
457: private Object createConnectionProxy(Object conn) {
458: Object cacheID = new Integer(conn.hashCode());
459: ObjectName targetName = getServiceName();
460: String proxyBindingName = null;
461: String jndiName = null;
462: Class[] ifaces = { java.sql.Connection.class };
463: ArrayList interceptorClasses = new ArrayList();
464: interceptorClasses.add(ClientMethodInterceptor.class);
465: interceptorClasses.add(InvokerInterceptor.class);
466: ClassLoader loader = Thread.currentThread()
467: .getContextClassLoader();
468: GenericProxyFactory proxyFactory = new GenericProxyFactory();
469: Object connProxy = proxyFactory.createProxy(cacheID,
470: targetName, delegateInvoker, jndiName,
471: proxyBindingName, interceptorClasses, loader, ifaces);
472: connectionMap.put(cacheID, conn);
473: log.debug("Created Connection proxy for invoker="
474: + jmxInvokerName + ", targetName=" + targetName
475: + ", cacheID=" + cacheID);
476: return connProxy;
477: }
478:
479: private Object createStatementProxy(Object stmt) {
480: Object cacheID = new Integer(stmt.hashCode());
481: ObjectName targetName = getServiceName();
482: String proxyBindingName = null;
483: String jndiName = null;
484: // Filter out all but java* interfaces
485: Class[] ifaces = getJavaInterfaces(stmt.getClass());
486: ArrayList interceptorClasses = new ArrayList();
487: interceptorClasses.add(StatementInterceptor.class);
488: interceptorClasses.add(ClientMethodInterceptor.class);
489: interceptorClasses.add(InvokerInterceptor.class);
490: ClassLoader loader = Thread.currentThread()
491: .getContextClassLoader();
492: GenericProxyFactory proxyFactory = new GenericProxyFactory();
493: Object stmtProxy = proxyFactory.createProxy(cacheID,
494: targetName, delegateInvoker, jndiName,
495: proxyBindingName, interceptorClasses, loader, ifaces);
496: statementMap.put(cacheID, stmt);
497: log.debug("Created Statement proxy for invoker="
498: + jmxInvokerName + ", targetName=" + targetName
499: + ", cacheID=" + cacheID);
500: return stmtProxy;
501: }
502:
503: private Object createResultSetProxy(Object results) {
504: Object cacheID = new Integer(results.hashCode());
505: ObjectName targetName = getServiceName();
506: String proxyBindingName = null;
507: String jndiName = null;
508: // Filter out all but java* interfaces
509: Class[] ifaces = getJavaInterfaces(results.getClass());
510:
511: ArrayList interceptorClasses = new ArrayList();
512: interceptorClasses.add(ClientMethodInterceptor.class);
513: interceptorClasses.add(InvokerInterceptor.class);
514: ClassLoader loader = Thread.currentThread()
515: .getContextClassLoader();
516: GenericProxyFactory proxyFactory = new GenericProxyFactory();
517: Object resultsProxy = proxyFactory.createProxy(cacheID,
518: targetName, delegateInvoker, jndiName,
519: proxyBindingName, interceptorClasses, loader, ifaces);
520: resultSetMap.put(cacheID, results);
521: log.debug("Created ResultSet proxy for invoker="
522: + jmxInvokerName + ", targetName=" + targetName
523: + ", cacheID=" + cacheID);
524: return resultsProxy;
525: }
526:
527: private Object createLobProxy(Object results) {
528: Object cacheID = new Integer(results.hashCode());
529: ObjectName targetName = getServiceName();
530: String proxyBindingName = null;
531: String jndiName = null;
532: Class[] ifaces = results.getClass().getInterfaces();
533: ArrayList interceptorClasses = new ArrayList();
534: interceptorClasses.add(ClientMethodInterceptor.class);
535: interceptorClasses.add(InvokerInterceptor.class);
536: ClassLoader loader = Thread.currentThread()
537: .getContextClassLoader();
538: GenericProxyFactory proxyFactory = new GenericProxyFactory();
539: Object resultsProxy = proxyFactory.createProxy(cacheID,
540: targetName, delegateInvoker, jndiName,
541: proxyBindingName, interceptorClasses, loader, ifaces);
542: lobMap.put(cacheID, results);
543: log
544: .debug("Created LOB proxy for invoker="
545: + jmxInvokerName + ", targetName=" + targetName
546: + ", cacheID=" + cacheID);
547: return resultsProxy;
548: }
549:
550: private Object createDatabaseMetaData(Object dbMetaData) {
551: Object cacheID = new Integer(dbMetaData.hashCode());
552: ObjectName targetName = getServiceName();
553: String proxyBindingName = null;
554: String jndiName = null;
555: Class[] ifaces = { java.sql.DatabaseMetaData.class };
556: ArrayList interceptorClasses = new ArrayList();
557: interceptorClasses.add(ClientMethodInterceptor.class);
558: interceptorClasses.add(InvokerInterceptor.class);
559: ClassLoader loader = Thread.currentThread()
560: .getContextClassLoader();
561: GenericProxyFactory proxyFactory = new GenericProxyFactory();
562: Object dbMetaDataProxy = proxyFactory.createProxy(cacheID,
563: targetName, delegateInvoker, jndiName,
564: proxyBindingName, interceptorClasses, loader, ifaces);
565: databaseMetaDataMap.put(cacheID, dbMetaData);
566: log.debug("Created DatabaseMetadata proxy for invoker="
567: + jmxInvokerName + ", targetName=" + targetName
568: + ", cacheID=" + cacheID);
569: return dbMetaDataProxy;
570:
571: }
572:
573: private void displayHashes(Map m) {
574: if (trace == false)
575: return;
576:
577: Iterator keys = m.keySet().iterator();
578: while (keys.hasNext()) {
579: Long key = (Long) keys.next();
580: log.trace(key + "=" + m.get(key));
581: }
582: }
583:
584: private Class[] getJavaInterfaces(Class clazz) {
585: ArrayList tmp = new ArrayList();
586: Classes.getAllInterfaces(tmp, clazz);
587: Iterator iter = tmp.iterator();
588: while (iter.hasNext()) {
589: Class c = (Class) iter.next();
590: if (c.getName().startsWith("java") == false)
591: iter.remove();
592: }
593: Class[] ifaces = new Class[tmp.size()];
594: return (Class[]) tmp.toArray(ifaces);
595: }
596: }
|