I need to execute several initialization statements on each borrowed connection from a Tomcat JDBC Pool. I cannot use JDBCInterceptors because pool is shared with other applications that won't need said initilization.
I'm using Spring Boot 1.4.4, deploying on Tomcat 8.5.11, which has a ResourceLink in context.xml to a Resource in server.xml that defines the DataSource against a Oracle 11g Database. I'm accessing the DataSource via JNDI.
As per this answer https://stackoverflow.com/a/38746398 I wanted to use Spring AOP to accomplish my goal.
I have created and Aspect that works perfectly if the DataSource is defined directly in context.xml, but fails with a ClassCastException if referenced via a ResourceLink to the same definition in server.xml
The exception is:
java.lang.ClassCastException: org.apache.tomcat.jdbc.pool.DataSource cannot be cast to org.apache.tomcat.jdbc.pool.DataSourceProxy
at org.apache.tomcat.jdbc.pool.DataSourceProxy$$FastClassBySpringCGLIB$$26808f96.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:721)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:52)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:656)
at org.apache.tomcat.jdbc.pool.DataSource$$EnhancerBySpringCGLIB$$17f85659.getConnection(<generated>)
My Aspect class:
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class DataSourceAspect {
private static final Logger LOG = LoggerFactory.getLogger(DataSourceAspect.class);
#AfterReturning(pointcut = "execution(* org.apache.tomcat.jdbc.pool.DataSourceProxy.getConnection())")
public void afterConnectionEstablished() {
LOG.info("Borrowed connection from the pool. Initializing...");
}
}
ResourceLink definition in context.xml:
<ResourceLink name="jdbc/us_j2eeCoreDS"
global="jdbc/us_j2eeCoreDS"
type="javax.sql.DataSource"/>
DataSource definition in server.xml (changed private values):
<Resource name="jdbc/us_j2eeCoreDS" type="javax.sql.DataSource"
global="jdbc/us_j2eeCoreDS"
factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
driverClassName="oracle.jdbc.OracleDriver"
username="xxx" password="xxx"
initialSize="1"
minIdle="1"
maxIdle="4" maxWaitMillis="5000"
maxActive="40"
removeAbandonedOnBorrow="true" removeAbandonedTimeout="300"
testWhileIdle="true"
validationQuery="SELECT 1 FROM DUAL"
timeBetweenEvictionRunsMillis="60000"
url="jdbc:oracle:thin:#host:port:SID"/>
As I said before, if I define the exact same DataSource in context.xml, it works flawlessly.
Any clue?
Thanks a lot.
Eventually the problem was not AOP related, but a dependency conflict.
The project is based on Spring Boot 1.4.4.RELEASE. In my build.gradle, I had the following dependency:
compile("org.springframework.boot:spring-boot-starter-data-jpa")
Being a starter, it's just a quick way to get more needed dependencies. One of the deps provided is:
org.springframework.boot:spring-boot-starter-jdbc:1.4.4.RELEASE
Which provides:
org.apache.tomcat:tomcat-jdbc:8.5.11
So as it turns out my war package ended up containing tomcat-jdbc-8.5.11.jar.
Tomcat 8.5.11 server also contained the same library, tomcat-jdbc.jar.
As the pool was server.xml defined, the library used by the pool was tomcat-jdbc.jar, but my application, having tomcat-jdbc-8.5.11.jar inside its WEB-INF/libs directory, used tomcat-jdbc-8.5.11.jar, leading to the ClassCastException. I didn't have time to dig out more and find the actual reason why it worked if the pool was defined in context.xml, but I guess it's just a case of jar loading priority.
The fix: exclude the tomcat-jdbc-8.5.11.jar from the war package, using the following gradle enchanment (just include it in your build.gradle configurations section):
configurations {
runtime.exclude module: 'tomcat-jdbc'
}
Hope this helps somebody else!
Related
I am try to convert a WAS traditional 8.5 websphere configuration to a websphere liberty 20.x configuration and getting an error. I believe the connection settings are right, the names are correct, the jar file for the driver exists but getting the error below.
Here is essentially the configuration.
<dataSource jndiName="jdbc/db2a" type="javax.sql.DataSource">
<jdbcDriver javax.sql.DataSource="com.ibm.db2.jcc.DB2Driver" libraryRef="DB2JCCLib"/>
<properties.db2.jcc driverType="4" databaseName="DB1" serverName="host.name" portNumber="446"/>
</dataSource>
<web-bnd
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://websphere.ibm.com/xml/ns/javaee"
xsi:schemaLocation="http://websphere.ibm.com/xml/ns/javaee http://websphere.ibm.com/xml/ns/javaee/ibm-web-bnd_1_0.xsd" version="1.0">
<virtual-host name="default_host"/>
<resource-ref name="jdbc/db2a" binding-name="jdbc/db2a"/>
</web-bnd>
...
Configuration at top of server.xml
<!-- Enable features -->
<featureManager>
<feature>appSecurity-2.0</feature>
<feature>jaxrs-2.0</feature>
<feature>jsp-2.3</feature>
<feature>localConnector-1.0</feature>
<feature>jaxws-2.2</feature>
<feature>ldapRegistry-3.0</feature>
</featureManager>
The jar file is : db2jcc4-4.22.29.jar
And has as reference to that jar / class file.
The java code for the connection is standard jdbc connection:
DriverManager.getConnection (connStr);
And this:
InitialContext initialContext = new InitialContext();
Context context = (Context)initialContext.lookup("java:comp/env");
DataSource dataSource = (DataSource)context.lookup(string5);
logger.info((Object)"CVDBBackendHandler.getConnection() out");
return dataSource.getConnection();
The error is below.
Root exception is com.ibm.wsspi.injectionengine.InjectionException: CWNEN0030E: The server was unable to obtain an object instance for the java:comp/env/jdbc/db2a reference. The exception message was: CWNEN1003E: The server was unable to find the jdbc/db2a binding with the java.lang.Object type for the java:comp/env/jdbc/db2a reference.
You need to add one of the JDBC features to your feature list, e.g.
<featureManager>
<feature>jdbc-4.3</feature>
</featureManager>
(or one of the earlier JDBC features, e.g.jdbc-4.2, etc.).
Though your messages.log should show a feature set like this (including features pulled in from the features you explicitly enabled):
CWWKF0012I: The server installed the following features: [appSecurity-2.0, distributedMap-1.0, el-3.0, federatedRegistry-1.0, jaxb-2.2, jaxrs-2.0, jaxrsClient-2.0, jaxws-2.2, jndi-1.0, json-1.0, jsp-2.3, ldapRegistry-3.0, localConnector-1.0, servlet-3.1, ssl-1.0].
this list doesn't include any activating the JDBC feature, so you must explicitly enable it. (On the other hand, note the jndi-1.0 feature required to do JNDI lookups is included even though it wasn't one you added directly to server.xml, since one of the other features included it).
Some helpful links for reference:
https://openliberty.io/docs/latest/reference/feature/jdbc-4.3.html
https://openliberty.io/docs/latest/relational-database-connections-JDBC.html
There is an error in the configuration. com.ibm.db2.jcc.DB2Driver is not an implementation of javax.sql.DataSource.
You can remove the configuration attribute javax.sql.DataSource="com.ibm.db2.jcc.DB2Driver" and let the built-in knowledge in Liberty infer it from the JDBC driver jar, or you can specify it as,
javax.sql.DataSource="com.ibm.db2.jcc.DB2DataSource"
The above data source class name can be found in DB2 documentation here
I am deploying a WAR to JBoss EAP 7. In my WAR's META-INF/context.xml file I have the following:
<Context unloadDelay="500000">
<Resource name="jdbc/sybase/somedb"
auth="Container"
type="javax.sql.DataSource"
driverClassName="net.sourceforge.jtds.jdbc.Driver"
url="jdbc:jtds:sybase://localhost:12501/somedb"
username="username" password="secret"
validationQuery="select 1"
maxActive="2" maxIdle="0" maxWait="-1"/>
...
From my Java code I try to obtain the DataSource doing a:
InitialContext cxt = new InitialContext();
DataSource ds = (DataSource) cxt.lookup( "java:/comp/env/jdbc/sybase/somedb" );
The exact above code works and the name is found in the context when I deploy to Tomcat 8 but not when I deploy to JBoss EAP 7. In the latter case I get:
Caused by: javax.naming.NameNotFoundException: comp/env/jdbc/sybase/somedb -- service jboss.naming.context.java.comp.env.jdbc.sybase.somedb
at org.jboss.as.naming.ServiceBasedNamingStore.lookup(ServiceBasedNamingStore.java:106)
at org.jboss.as.naming.NamingContext.lookup(NamingContext.java:207)
at org.jboss.as.naming.InitialContext$DefaultInitialContext.lookup(InitialContext.java:235)
at org.jboss.as.naming.NamingContext.lookup(NamingContext.java:193)
at org.jboss.as.naming.NamingContext.lookup(NamingContext.java:189)
at javax.naming.InitialContext.lookup(InitialContext.java:417)
at javax.naming.InitialContext.lookup(InitialContext.java:417)
What am I doing wrong and how can I fix the above problem?
Your META-INF/context.xml file is a Tomcat deployment descriptor (not defined by the Java EE specification) so it is not seen or parsed by JBoss EAP 7.
There are many alternatives to this including the solution to is there a standard way to define a JDBC Datasource for Java EE containers.
If you were to ask RedHat support they would likely recommend that you create the datasource using server administration tools such as the admin console or jboss-cli.sh. This decouples your application from the datasource definition so that you can specify environment specific settings (such as pool sizes and hostnames) without repackaging your WAR.file. This method also requires you to deploy the JDBC driver jar separately from your application.
Trying to get WorkManagers working with CommonJ in a Spring Boot app, hosted in TomEE.
Currently have the following configuration:
Tomcat context.xml
<Context>
<Resource name="myWorkManager"
auth="Container"
type="commonj.work.WorkManager"
factory="de.myfoo.commonj.work.FooWorkManagerFactory"
maxThreads="5" />
<ResourceLink
name="myWorkManager"
global="myWorkManager"
type="commonj.work.WorkManager" />
</Context>
Spring app web.xml
<resource-ref>
<res-ref-name>myWorkManager</res-ref-name>
<res-type>commonj.work.WorkManager</res-type>
<res-auth>Container</res-auth>
</resource-ref>
This is currently throwing the following exception when the app loads:
Caused by: org.springframework.jndi.TypeMismatchNamingException: Object of type [class de.myfoo.commonj.work.FooWorkManager] available at JNDI location [java:comp/env/myWorkManager] is not assignable to [commonj.work.WorkManager]
at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:182)
at org.springframework.jndi.JndiLocatorSupport.lookup(JndiLocatorSupport.java:95)
at org.springframework.scheduling.commonj.WorkManagerTaskExecutor.afterPropertiesSet(WorkManagerTaskExecutor.java:110)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1637)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1574)
... 53 more
I have the CommonJ jars downloaded from http://commonj.myfoo.de/install.shtml in my Tomcat lib directory.
I feel like I'm getting pretty close but slightly puzzled by this exception.
Any help would be much appreciated.
UPDATE
If I remove the two CommonJ jars from TomEE lib folder, I get this exception
Caused by: java.lang.ClassNotFoundException: commonj.work.WorkManager
Which is what I would expect.
If I remove the factory property from the resource element I get:
Caused by: org.springframework.jndi.TypeMismatchNamingException:
Object of type [class org.apache.openejb.core.ivm.naming.IvmContext]
available at JNDI location [java:comp/env/wm/default] is not
assignable to [commonj.work.WorkManager]
Im encounter with same issue, when try to start my app locally in maven-jetty-plugin. M. Deinum comment was very helpful. This error happens if you have lib jar in shared lib of your Application Server and in your WEB-INF/lib folder of web application, because server use one jar to create resource (parent classloader), but application use self jar(child classloader) and it two different classes hierarchy, so FooWorkManager cant be cast to WorkManager.
I was interested to see the performance of Firebird with Hibernate, but I can not manage to make it run correctly. I added to my pom.xml:
<dependency>
<groupId>net.sf.squirrel-sql.thirdparty-non-maven</groupId>
<artifactId>jaybird</artifactId>
<version>2.1.6</version>
</dependency>
When running it I keep getting:
java.lang.ClassNotFoundException: javax.resource.ResourceException
I tried adding all kinds of javax, javaee and others dependencies (via Maven), but I can't manage to run it. With PostgreSQL I have no issues and everything works as it is supposed to.
Running on Apache Tomcat 7.0.26.
The 'problem' is that Jaybird internally depends on the JavaEE concept of a resource-adapter and therefor requires some classes from JavaEE (specifically one that includes the javax.resource package (and subpackages). You need to include a JavaEE jar, or use
<groupId>org.firebirdsql.jdbc</groupId>
<artifactId>jaybird-jdk18</artifactId>
<version>3.0.5</version>
This one should automatically download the required dependency.
If all else fails, download the distribution from http://www.firebirdsql.org/en/jdbc-driver/ and use the connector-api-1.5.jar from the lib folder.
BTW: I hope to eliminate this dependency in Jaybird 5.
Disclaimer: I am one of the developers of Jaybird
We'll i was able to figure this out:
you can use hibernate's own connection pool
you can use commons-dbcp connection pool
you can use new tomcat7's jdbc connection pool.
Assuming you want to use 3, there are few steps you must perform, if you use firebird
Download jdk from : http://sourceforge.net/projects/firebird/files/firebird-jca-jdbc-driver/2.2.0-release-jdk16/Jaybird-2.2.0JDK_1.6.zip/download
Extract archive, copy jaybird*.jar and connector-api*.jar to /usr/share/tomcat7/lib.
Inside the connector-api jar, there is the missing class ResourceException.
WARNING: do not copy huge j2eeapi.jar instead, beacuse they contain offending classes to servlet-api.jar from tomcat
If you installed tomcat7 from tar, that's all. If you use ubuntu apt to install tomcat's package, then download original tar from tomcat's download side, extract it and copy tomcat-jdbc to wherever is you tomcat's lib folder (for ubuntu, that is /usr/share/tomcat7/lib)
That's all. Put the configuration into context.xml and that's all
<Resource
name="jdbc/SOME_NAME"
auth="Container"
type="javax.sql.DataSource"
factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
testWhileIdle="true"
testOnBorrow="true"
testOnReturn="false"
validationQuery="SELECT 'NOW' from RDB$DATABASE"
validationInterval="30000"
timeBetweenEvictionRunsMillis="30000"
maxActive="100"
minIdle="10"
maxWait="10000"
initialSize="10"
removeAbandonedTimeout="60"
removeAbandoned="true"
logAbandoned="true"
minEvictableIdleTimeMillis="30000"
jmxEnabled="true"
jdbcInterceptors="org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer"
username="sysdba"
password="masterke"
driverClassName="org.firebirdsql.jdbc.FBDriver"
url="jdbc:firebirdsql:IP:ALIAS?lc_ctype=UTF-8"
/>
I am trying to configure the following on Tomcat.
Resource auth="Container" name="jdbc/yourDataSourceName"
driverClassName="oracle.jdbc.OracleDriver"
factory="oracle.jdbc.pool.OracleDataSourceFactory"
type="oracle.jdbc.pool.OracleDataSource"
connectionCacheProperties="{}"
connectionCachingEnabled="true"
user="foo"
password="bar"
url="jdbc:oracle:thin:#foo.bar.com:1521:foobar"
When I put the following in my app META-INF/context.xml file, I get the following error:
java.lang.ClassCastException: org.apache.tomcat.dbcp.dbcp.BasicDataSource cannot be cast to oracle.jdbc.pool.OracleDataSource
When I move this to $TOMCAT_HOME/conf/context.xml, it works fine. It is not letting me configure specific to the app. Any ideas?
I have ojdbc6.jar in $TOMCAT_HOME/lib as well as in WEB-INF/lib of my application folder. Please advise. Am I missing something? Thanks for your time.
You can try not to use specific Oracle classes such as "OracleDataSourceFactory" in your datasource configuration. It seems that Oracle Conn.pool is not compatible with DBCP.
Define driverClass only.
You can try something like this:
<Resource
name="jdbc/yourDataSourceName" auth="Container"
type="javax.sql.DataSource"
driverClassName="oracle.jdbc.OracleDriver"
url="jdbc:oracle:thin:#foo.bar.com:1521:foobar"
user="foo"
password="bar"
maxActive="5" maxIdle="1" maxWait="-1"/>
Hope it will help