From 2e65f4c7827678246e6f521189e0a470ca29a7fb Mon Sep 17 00:00:00 2001 From: Patrick Cheng Date: Sun, 19 Feb 2012 12:59:35 -0800 Subject: [PATCH 1/5] when using JNDI, will try to discover the real adapter. and centralize the special handling of the adapter for Sqlserver so we don't get error when using transaction. --- data_objects/lib/data_objects/connection.rb | 23 +-------- data_objects/lib/data_objects/transaction.rb | 2 +- data_objects/lib/data_objects/uri.rb | 51 ++++++++++++++++++++ 3 files changed, 54 insertions(+), 22 deletions(-) diff --git a/data_objects/lib/data_objects/connection.rb b/data_objects/lib/data_objects/connection.rb index 7876731c..54dcd50c 100644 --- a/data_objects/lib/data_objects/connection.rb +++ b/data_objects/lib/data_objects/connection.rb @@ -18,22 +18,10 @@ def self.new(uri_s) when :java warn 'JNDI URLs (connection strings) are only for use with JRuby' unless RUBY_PLATFORM =~ /java/ - driver = uri.query.delete('scheme') - driver = uri.query.delete('driver') - - conn_uri = uri.to_s.gsub(/\?$/, '') + conn_uri = uri.to_s.gsub(/\?.*$/, '') when :jdbc warn 'JDBC URLs (connection strings) are only for use with JRuby' unless RUBY_PLATFORM =~ /java/ - path = uri.subscheme - driver = if path.split(':').first == 'sqlite' - 'sqlite3' - elsif path.split(':').first == 'postgresql' - 'postgres' - else - path.split(':').first - end - conn_uri = uri_s # NOTE: for now, do not reformat this JDBC connection # string -- or, in other words, do not let # DataObjects::URI#to_s be called -- as it is not @@ -45,14 +33,7 @@ def self.new(uri_s) conn_uri = uri end - # Exceptions to how a driver class is determined for a given URI - driver_class = if driver == 'sqlserver' - 'SqlServer' - else - driver.capitalize - end - - clazz = DataObjects.const_get(driver_class)::Connection + clazz = DataObjects.adapter_name(uri)::Connection unless clazz.method_defined? :close if (uri.scheme.to_sym == :java) clazz.class_eval do diff --git a/data_objects/lib/data_objects/transaction.rb b/data_objects/lib/data_objects/transaction.rb index 487b5499..4eb4cbb6 100644 --- a/data_objects/lib/data_objects/transaction.rb +++ b/data_objects/lib/data_objects/transaction.rb @@ -18,7 +18,7 @@ class Transaction # Instantiate the Transaction subclass that's appropriate for this uri scheme def self.create_for_uri(uri) uri = uri.is_a?(String) ? URI::parse(uri) : uri - DataObjects.const_get(uri.scheme.capitalize)::Transaction.new(uri) + DataObjects.adapter_name(uri)::Transaction.new(uri) end # diff --git a/data_objects/lib/data_objects/uri.rb b/data_objects/lib/data_objects/uri.rb index a1aae206..a3f068ac 100644 --- a/data_objects/lib/data_objects/uri.rb +++ b/data_objects/lib/data_objects/uri.rb @@ -2,6 +2,57 @@ module DataObjects + def self.adapter_name(uri) + + adapter = uri.scheme + case adapter + when 'java' + adapter = uri.query['adapter'] + unless adapter + # discover the real adapter + jndi_uri = "#{uri.scheme}:#{uri.path}" + context = javax.naming.InitialContext.new + ds= context.lookup(jndi_uri) + begin + conn = ds.getConnection + metadata = conn.getMetaData + driver_name = metadata.getDriverName + + adapter = case driver_name + when /mysql/i then 'mysql' + when /oracle/i then 'oracle' + when /postgres/i then 'postgres' + when /sqlite/i then 'sqlite3' + when /sqlserver|tds|Microsoft SQL/i then 'sqlserver' + else + nil # not supported + end # case + ensure + conn.close + end + end + when 'jdbc' + path = uri.subscheme + driver = if path.split(':').first == 'sqlite' + 'sqlite3' + elsif path.split(':').first == 'postgresql' + 'postgres' + else + path.split(':').first + end + end + + # Exceptions to how a adapter class is determined for a given URI + adapter_class = if adapter == 'sqlserver' + 'SqlServer' + else + adapter.capitalize + end + + const_get(adapter_class) + end + + # A DataObjects URI is of the form scheme://user:password@host:port/path#fragment # # The elements are all optional except scheme and path: From b37a48f570c0c01d24e16c76a03306d3c7aeb838 Mon Sep 17 00:00:00 2001 From: Patrick Cheng Date: Tue, 21 Feb 2012 19:53:49 -0800 Subject: [PATCH 2/5] conn needs to be outside begin-ensure-end otherwise, conn inside ensure block would be nil --- data_objects/lib/data_objects/uri.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_objects/lib/data_objects/uri.rb b/data_objects/lib/data_objects/uri.rb index a3f068ac..b9b93810 100644 --- a/data_objects/lib/data_objects/uri.rb +++ b/data_objects/lib/data_objects/uri.rb @@ -13,8 +13,8 @@ def self.adapter_name(uri) jndi_uri = "#{uri.scheme}:#{uri.path}" context = javax.naming.InitialContext.new ds= context.lookup(jndi_uri) + conn = ds.getConnection begin - conn = ds.getConnection metadata = conn.getMetaData driver_name = metadata.getDriverName From d01e45f0bb11290f180bd0868cde770627c8db03 Mon Sep 17 00:00:00 2001 From: Patrick Cheng Date: Tue, 21 Feb 2012 19:56:07 -0800 Subject: [PATCH 3/5] When using DBCP, the Statement returned is a DBCP's DelegatingStatement which cannot be casted as a OracleStatement. Need to unwrap it first. --- .../do_oracle/OracleDriverDefinition.java | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/do_oracle/ext-java/src/main/java/do_oracle/OracleDriverDefinition.java b/do_oracle/ext-java/src/main/java/do_oracle/OracleDriverDefinition.java index b52753a0..5bd5d2c1 100644 --- a/do_oracle/ext-java/src/main/java/do_oracle/OracleDriverDefinition.java +++ b/do_oracle/ext-java/src/main/java/do_oracle/OracleDriverDefinition.java @@ -2,6 +2,8 @@ import java.io.IOException; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.net.URI; import java.sql.Connection; @@ -137,6 +139,49 @@ public void setPreparedStatementParam(PreparedStatement ps, } } + protected Statement getRealStatement(Statement ps) { + try + { + // if the DataSource is wrapped by DBCP, + // we could be getting a 'org.apache.tomcat.dbcp.dbcp.DelegatingPreparedStatement' + + // in that case, we can get the real PreparedStatement by calling getDelegate + // use reflection because I don't want to introduce dependency to DBCP + + final Class clsDelgatingStatement = Class.forName("org.apache.tomcat.dbcp.dbcp.DelegatingStatement"); + + final Method methodGetDelegate = clsDelgatingStatement.getMethod("getDelegate"); + Object o = ps; + + while( clsDelgatingStatement.isInstance(o)) { + o = methodGetDelegate.invoke(o); + } + if (o instanceof Statement) { + ps = (Statement)o; + } + } catch (ClassNotFoundException e) { + // ignore + } catch (SecurityException e) { + // ignore + } catch (NoSuchMethodException e) { + // ignore + } catch (IllegalArgumentException e) { + // ignore + } catch (IllegalAccessException e) { + // ignore + } catch (InvocationTargetException e) { + Throwable t = e.getCause(); + if (t instanceof Error) { + throw (Error)t; + } + if (t instanceof RuntimeException) { + throw (RuntimeException)t; + } + } + + return ps; + } + /** * * @param sqlText @@ -147,7 +192,7 @@ public void setPreparedStatementParam(PreparedStatement ps, */ @Override public boolean registerPreparedStatementReturnParam(String sqlText, PreparedStatement ps, int idx) throws SQLException { - OraclePreparedStatement ops = (OraclePreparedStatement) ps; + OraclePreparedStatement ops = (OraclePreparedStatement)getRealStatement(ps); Pattern p = Pattern.compile("^\\s*INSERT.+RETURNING.+INTO\\s+", Pattern.CASE_INSENSITIVE); Matcher m = p.matcher(sqlText); if (m.find()) { @@ -165,7 +210,7 @@ public boolean registerPreparedStatementReturnParam(String sqlText, PreparedStat */ @Override public long getPreparedStatementReturnParam(PreparedStatement ps) throws SQLException { - OraclePreparedStatement ops = (OraclePreparedStatement) ps; + OraclePreparedStatement ops = (OraclePreparedStatement)getRealStatement(ps); ResultSet rs = ops.getReturnResultSet(); try { if (rs.next()) { @@ -293,6 +338,7 @@ public void afterConnectionCallback(IRubyObject doConn, Connection conn, Map Date: Fri, 24 Feb 2012 15:09:31 -0800 Subject: [PATCH 4/5] broke data mapper to sqlite. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DataMapper expects Sqlite adapter, but DataObject expects Sqlite3. There is code in dm-sqlite-adapter to change the adapter from sqlite to sqlite3, and the dm-do-adapter to create URI using adapter instead of schemeā€¦. but that breaks JNDI case, when we want to scheme to stay 'java', but adapter to be whatever the underlying adapter is. consolidated more of the logic that maps DM constants to DO constants and changing DriverDefinition's verifyScheme to check for both scheme and jdbcScheme (in the case of 'sqlite' vs. 'sqlite3'). --- data_objects/lib/data_objects/connection.rb | 1 - data_objects/lib/data_objects/uri.rb | 17 ++++++++--------- .../drivers/AbstractDriverDefinition.java | 2 +- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/data_objects/lib/data_objects/connection.rb b/data_objects/lib/data_objects/connection.rb index 54dcd50c..e487fa40 100644 --- a/data_objects/lib/data_objects/connection.rb +++ b/data_objects/lib/data_objects/connection.rb @@ -29,7 +29,6 @@ def self.new(uri_s) # java.sql.DriverManager.getConnection to throw a # 'No suitable driver found for...' exception. else - driver = uri.scheme conn_uri = uri end diff --git a/data_objects/lib/data_objects/uri.rb b/data_objects/lib/data_objects/uri.rb index b9b93810..12fc2622 100644 --- a/data_objects/lib/data_objects/uri.rb +++ b/data_objects/lib/data_objects/uri.rb @@ -33,18 +33,17 @@ def self.adapter_name(uri) end when 'jdbc' path = uri.subscheme - driver = if path.split(':').first == 'sqlite' - 'sqlite3' - elsif path.split(':').first == 'postgresql' - 'postgres' - else - path.split(':').first - end + adapter = path.split(':').first end - # Exceptions to how a adapter class is determined for a given URI - adapter_class = if adapter == 'sqlserver' + # Exceptions to how a adapter class is determined for a given URI + adapter_class = case adapter + when 'sqlserver' 'SqlServer' + when /sqlite/ + 'Sqlite3' + when /postgres/ + 'Postgres' else adapter.capitalize end diff --git a/do_jdbc/src/main/java/data_objects/drivers/AbstractDriverDefinition.java b/do_jdbc/src/main/java/data_objects/drivers/AbstractDriverDefinition.java index 77ed0c89..59c8171f 100644 --- a/do_jdbc/src/main/java/data_objects/drivers/AbstractDriverDefinition.java +++ b/do_jdbc/src/main/java/data_objects/drivers/AbstractDriverDefinition.java @@ -211,7 +211,7 @@ public URI parseConnectionURI(IRubyObject connection_uri) * @param scheme */ protected void verifyScheme(String scheme) { - if (!this.scheme.equals(scheme)) { + if (!this.scheme.equals(scheme) && !this.jdbcScheme.equals(scheme)) { throw new RuntimeException("scheme mismatch, expected: " + this.scheme + " but got: " + scheme); } From e5ea5f6926b52629453fc94086999afcbb0fbef3 Mon Sep 17 00:00:00 2001 From: Patrick Cheng Date: Mon, 12 Mar 2012 21:18:22 -0700 Subject: [PATCH 5/5] now works with Oracle JDBC driver 10.2.0.1.0. Avoided references to OraclePreparedStatement completely. Apparently there are multiple definition of the class, and it moves around. --- .../do_oracle/OracleDriverDefinition.java | 82 +++++++++++++++++-- 1 file changed, 77 insertions(+), 5 deletions(-) diff --git a/do_oracle/ext-java/src/main/java/do_oracle/OracleDriverDefinition.java b/do_oracle/ext-java/src/main/java/do_oracle/OracleDriverDefinition.java index 5bd5d2c1..3d6c80bc 100644 --- a/do_oracle/ext-java/src/main/java/do_oracle/OracleDriverDefinition.java +++ b/do_oracle/ext-java/src/main/java/do_oracle/OracleDriverDefinition.java @@ -12,7 +12,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; -import oracle.jdbc.OraclePreparedStatement; import oracle.jdbc.OracleTypes; import java.util.Properties; @@ -139,6 +138,78 @@ public void setPreparedStatementParam(PreparedStatement ps, } } + protected static boolean isMethodEquals(Method method, String methodName, Object... parameters) + { + if (! methodName.equals(method.getName())) { + return false; + } + + if (parameters != null) { + Class []parameterTypes = method.getParameterTypes(); + + if (parameterTypes.length != parameters.length) { + return false; + } + + for(int i = 0; i < parameters.length; i++) { + Object parameter = parameters[i]; + Class parameterClass = parameter.getClass(); + Class parameterType = parameterTypes[i]; + + if (parameterType.isPrimitive()) { + + if (parameterType == Integer.TYPE) { + parameterType = Integer.class; + } + if (parameterType == Float.TYPE) { + parameterType = Float.class; + } + if (parameterType == Double.TYPE) { + parameterType = Double.class; + } + if (parameterType == Byte.TYPE) { + parameterType = Byte.class; + } + if (parameterType == Character.TYPE) { + parameterType = Character.class; + } + if (parameterType == Short.TYPE) { + parameterType = Short.class; + } + if (parameterType == Long.TYPE) { + parameterType = Long.class; + } + } + if (! parameterType.isAssignableFrom(parameterClass)) { + return false; + } + } + } + return true; + } + + protected static Object invoke(Object object, String methodName, Object... parameters) + { + Class klass = object.getClass(); + + Method []methods = klass.getMethods(); + + for( Method method : methods) { + + if (isMethodEquals(method, methodName, parameters)) + { + try { + return method.invoke(object, parameters); + } catch (IllegalAccessException e) { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } catch (InvocationTargetException e) { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + } + } + return null; + } + protected Statement getRealStatement(Statement ps) { try { @@ -192,11 +263,12 @@ protected Statement getRealStatement(Statement ps) { */ @Override public boolean registerPreparedStatementReturnParam(String sqlText, PreparedStatement ps, int idx) throws SQLException { - OraclePreparedStatement ops = (OraclePreparedStatement)getRealStatement(ps); + PreparedStatement ops = (PreparedStatement)getRealStatement(ps); + Pattern p = Pattern.compile("^\\s*INSERT.+RETURNING.+INTO\\s+", Pattern.CASE_INSENSITIVE); Matcher m = p.matcher(sqlText); if (m.find()) { - ops.registerReturnParameter(idx, Types.BIGINT); + invoke(ps, "registerReturnParameter", idx, Types.BIGINT); return true; } return false; @@ -210,8 +282,8 @@ public boolean registerPreparedStatementReturnParam(String sqlText, PreparedStat */ @Override public long getPreparedStatementReturnParam(PreparedStatement ps) throws SQLException { - OraclePreparedStatement ops = (OraclePreparedStatement)getRealStatement(ps); - ResultSet rs = ops.getReturnResultSet(); + PreparedStatement ops = (PreparedStatement)getRealStatement(ps); + ResultSet rs = (ResultSet) invoke(ps, "getReturnResultSet"); try { if (rs.next()) { // Assuming that primary key will not be larger as long max value