(long tweet) Undeploy issue with Tomcat on Windows
Case
I had the following issue: when I undeployed the WAR from Tomcat using the manager instance, the undeploy failed. As a workaround, I had to restart Tomcat for the undeploy to be taken in account.
This issue occured only in Windows ; when the exact same WAR and the same version of Tomcar on Debian, I was able to deploy and undeploy many times.
Quick Fix
In the %CATALINA_HOME%\conf\context.xml
, replace:
[xml]<Context>[/xml]
with:
[xml]<Context antijarlocking="true" antiResourceLocking="true"/>[/xml]
(long tweet) How to use comments in JSF 2?
If you write comments in an XHTML file (used by JSF2) as regular XML comments (ie starting with <!--
and ending with -->
), you may probably find them in the HTML source generated “by” JSF. To prevent that, add the following bloxk in your web.xml
:
[xml]<context-param>
<param-name>facelets.SKIP_COMMENTS</param-name>
<param-value>true</param-value>
</context-param>[/xml]
(quick tutorial) Migration from MySQL to HSQLDB
Case
I got the project described in MK Yong’s website. This projects is a sample code of JSF + Spring + Hibernate. The laying DB is a MySQL. For many reasons, I’d rather not to detail, I prefered to a HSQLDB instead of the MySQL.
(Notice: the zip you can download at MK Yong’s website contains many errors, not related to the persistance layer but to JSF itself.)
How to migrate any project from MySQL to HSQLDB?
Solution
You have to follow these steps:
Maven
In the pom.xml
, replace:
[xml]<!– MySQL database driver –>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
[/xml]
with:
[xml] <!– driver for HSQLdb –>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.2.8</version>
</dependency>
[/xml]
By the way, you can add Jetty plugin to have shorter development cycles:
[xml] <plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<configuration>
<webApp>${basedir}/target/jsf.war</webApp>
<port>8088</port>
</configuration>
</plugin>[/xml]
Persistence
Properties
Replace the content of db.properties
file with:[java]jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://localhost:9001
jdbc.username=sa
jdbc.password=[/java]
Hibernate Mapping
In the *.hbm.xml
files:
- in the tag
<class>
, remove the attributecatalog="..."
- replace the types with fully qualified object types, eg:
long
withjava.lang.Long
,string
withjava.lang.String
,timestamp
withjava.util.Date
,- etc.
Hibernate Properties
For the property of key hibernate.dialect
, replace the value: org.hibernate.dialect.MySQLDialectorg.hibernate.dialect.HSQLDialect
with the value: org.hibernate.dialect.HSQLDialect
.
To match my needs, I tuned Hibernate properties a bit more, but it may not be needed in all situations:
[xml] <property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hbm2ddl.auto">create-drop</prop>
<prop key="hibernate.hbm2ddl.auto">create-drop</prop>
<prop key="connection.pool_size">1</prop>
<prop key="current_session_context_class">thread</prop>
<prop key="cache.provider_class">org.hibernate.cache.NoCacheProvider</prop>
</props>
</property>
[/xml]
Run
Run the HSQLDB server. IMHO, the quickest is to run the following Maven command:
mvn exec:java -Dexec.mainClass="org.hsqldb.Server"
But you may prefer the old school java -cp hsqldb-XXX.jar org.hsqldb.Server
;-).
Tip! To get a GUI to check the content of the DB instance, you can run:
mvn exec:java -Dexec.mainClass="org.hsqldb.util.DatabaseManager"
Then build and launch Jetty:
mvn clean install jetty:run-exploded
Here you can see the great feature of HSQLDB, that will allow you to create, alter and delete tables on the fly (if hibernate.hbm2ddl.auto
is set to create-drop
), without any SQL scripts, but only thanks to HBM mapping files.
(long tweet) Jetty / Unified Expression Language / NoSuchMethodError: javax.el.ELResolver.invoke
Case
On deploying a JSF project on Jetty (via Maven plugin), when I display the XHtml page I get an error, which starts with:
[java]java.lang.NoSuchMethodError: javax.el.ELResolver.invoke(Ljavax/el/ELContext;Ljava/lang/Object;Ljava/lang/Object;[Ljava/lang/Class;[Ljava/lang/Object;)Ljava/lang/Object;
at com.sun.el.parser.AstValue.getValue(AstValue.java:111)
at com.sun.el.parser.AstValue.getValue(AstValue.java:163)
at com.sun.el.ValueExpressionImpl.getValue(ValueExpressionIm[/java]
Quick fix
I fixed the issue by removing the dependencies to Unified Expression Language (EL API), either direct or induced:
[xml]
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>el-impl</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.sun.el</groupId>
<artifactId>el-ri</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>el-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>com.sun.el</groupId>
<artifactId>el-ri</artifactId>
<version>1.0</version>
</dependency>[/xml]
Actually, some JARs that had to be depended on have been included within Tomcat and Jetty, that’s why some conflict of JARs happen to be. What prevented me to fix quickly the issue was that Tomcat and Jetty seem not to embed the same version on EL
Unable to instantiate default tuplizer… java.lang.NoSuchMethodError: org.objectweb.asm.ClassWriter.
Case
On running a web application hosted on Jetty, I get the following stracktrace:
[java]Nested in org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘sessionFactory’ defined in ServletContext resource [/WEB-INF/classes/config/spring/beans/HibernateSessionFactory.xml]: Invocation of init method failed; nested exception is org.hibernate.HibernateException: Unable to instantiate default tuplizer [org.hibernate.tuple.entity.PojoEntityTuplizer]:
java.lang.NoSuchMethodError: org.objectweb.asm.ClassWriter.<init>(I)V[/java]
Unlike what I immediatly thought at first glance, the problem is not induced by the Tuplizer ; the actual error is hidden at the bottom: java.lang.NoSuchMethodError: org.objectweb.asm.ClassWriter.
Here are some of the dependencies:
[java]org.hsqldb:hsqldb:jar:2.2.8:compile
org.springframework:spring:jar:2.5.6:compile
org.hibernate:hibernate:jar:3.2.7.ga:compile
javax.transaction:jta:jar:1.0.1B:compile
| +- asm:asm-attrs:jar:1.5.3:compile
| \- asm:asm:jar:1.5.3:compile[/java]
Fix
Main fix
The case is a classic problem of inherited depencencies. To fix it, you have to excluse ASM 1.5.3, and replace it with more recent version. In the pom.xml
, you would then have:
[xml]
<properties>
<spring.version>3.1.0.RELEASE</spring.version>
<hibernate.version>3.2.7.ga</hibernate.version>
<asm.version>3.1</asm.version>
</properties>
…
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate</artifactId>
<version>${hibernate.version}</version>
<exclusions>
<exclusion>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
</exclusion>
<exclusion>
<groupId>asm</groupId>
<artifactId>asm-attrs</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>${asm.version}</version>
</dependency>
[/xml]
Other improvements
I took the opportunity to upgrade Spring 2.5 to Spring 3.1 (cf the properties above).
Besides, I modified the *.hbm.xml files to use object types, rather than primary types, eg replacing:
[xml]<id name="jonathanId" type="long">[/xml]
with:
[xml]<id name="jonathanId" type="java.lang.Long">[/xml]
(long tweet) Failure to find javax.transaction:jta:jar:1.0.1B
Case
Getting and building a project got on the internet, I got the following stractrace:
[java]
[ERROR] Failed to execute goal on project skillsPoC: Could not resolve dependencies for project lalou.jonathan.poc:skillsPoC:war:1.0-SNAPSHOT: Failure to find javax.transaction:jta:jar:1.0.1B in http://192.168.0.39:8081/nexus/content/repositories/central/ was cached in the local repository, resolution will not be reattempted until the update interval of localRepository has elapsed or updates are
forced -> [Help 1]
[ERROR][/java]
Actually, the needed JAR (javax.transaction:jta:jar:1.0.1B
) is depended on by Spring 2.5.
Quick fix
- Add the following repository in your
pom.xml
:[xml] <repository>
<id>java.net.m2</id>
<name>java.net m2 repo</name>
<url>http://download.java.net/maven/2</url>
</repository>[/xml] - Unlike what I could read on Padova’s JUG blog, you need not get and install manually the jar any longer.
- Possibly, you may have to disable the block related to the
<mirrors>
in yoursettings.xml
.
(long tweet) This page calls for XML namespace declared with prefix body but no taglibrary exists for that namespace.
Case
On creating a new JSF 2 page, I get the following warning when the page is displayed:
[java]Warning: This page calls for XML namespace declared with prefix body but no taglibrary exists for that namespace.[/java]
Fix
In the XHTML page, replace the HTML 4 headers:
[xml]<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>…</html>
[/xml]
with XHTML headers:
[xml]<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
…</html>
[/xml]
(long tweet) Add RichFaces to a Maven / JSF 2 project
Case
You have a JSF 2 project that you need upgrade with jBoss RichFaces and Ajax4JSF, (I assume the process is similar for other libraries, such as Primefaces, ICEfaces, etc.).
Quick Fix
In XHTML
In XHTML pages, add the namespaces related to Richfaces:[xml] xmlns:a4j="http://richfaces.org/a4j"
xmlns:rich="http://richfaces.org/rich"[/xml]
In Maven
In Maven’s pom.xml
, I suggest to add a property, such as:
[xml] <properties>
<richfaces.version>4.1.0.Final</richfaces.version>
</properties>[/xml]
Add the following dependency blocks:
[xml]<dependency>
<groupId>org.richfaces.ui</groupId>
<artifactId>richfaces-components-ui</artifactId>
<version>${richfaces.version}</version>
</dependency>
<dependency>
<groupId>org.richfaces.core</groupId>
<artifactId>richfaces-core-impl</artifactId>
<version>${richfaces.version}</version>
</dependency>[/xml]
LinkageError: loader constraint violation: loader (instance of XXX) previously initiated loading for a different type with name “YYY”
Case
While building a JSF 2 project on Maven 3, I got the following error:
LinkageError: loader constraint violation: loader (instance of org/codehaus/plexus/classworlds/realm/ClassRealm) previously initiated loading for a different type with name "javax/el/ExpressionFactory"
Complete Stacktrace:
[java]GRAVE: Critical error during deployment:
java.lang.LinkageError: loader constraint violation: loader (instance of org/codehaus/plexus/classworlds/realm/ClassRealm) previously initiated loading for a different type with name "javax/el/ExpressionFactory"
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
at org.codehaus.plexus.classworlds.realm.ClassRealm.loadClassFromSelf(ClassRealm.java:386)
at org.codehaus.plexus.classworlds.strategy.SelfFirstStrategy.loadClass(SelfFirstStrategy.java:42)
at org.codehaus.plexus.classworlds.realm.ClassRealm.loadClass(ClassRealm.java:244)
at org.codehaus.plexus.classworlds.realm.ClassRealm.loadClass(ClassRealm.java:230)
at org.apache.jasper.runtime.JspApplicationContextImpl.getExpressionFactory(JspApplicationContextImpl.java:80)
at com.sun.faces.config.ConfigureListener.registerELResolverAndListenerWithJsp(ConfigureListener.java:693)
at com.sun.faces.config.ConfigureListener.contextInitialized(ConfigureListener.java:243)
at org.mortbay.jetty.handler.ContextHandler.startContext(ContextHandler.java:540)
at org.mortbay.jetty.servlet.Context.startContext(Context.java:135)
at org.mortbay.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1220)
at org.mortbay.jetty.handler.ContextHandler.doStart(ContextHandler.java:510)
at org.mortbay.jetty.webapp.WebAppContext.doStart(WebAppContext.java:448)
at org.mortbay.jetty.plugin.Jetty6PluginWebAppContext.doStart(Jetty6PluginWebAppContext.java:110)
at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:39)
at org.mortbay.jetty.handler.HandlerCollection.doStart(HandlerCollection.java:152)
at org.mortbay.jetty.handler.ContextHandlerCollection.doStart(ContextHandlerCollection.java:156)
at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:39)
at org.mortbay.jetty.handler.HandlerCollection.doStart(HandlerCollection.java:152)
at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:39)
at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:130)
at org.mortbay.jetty.Server.doStart(Server.java:222)
at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:39)
at org.mortbay.jetty.plugin.Jetty6PluginServer.start(Jetty6PluginServer.java:132)
at org.mortbay.jetty.plugin.AbstractJettyMojo.startJetty(AbstractJettyMojo.java:371)
at org.mortbay.jetty.plugin.AbstractJettyMojo.execute(AbstractJettyMojo.java:307)
at org.mortbay.jetty.plugin.AbstractJettyRunMojo.execute(AbstractJettyRunMojo.java:203)
at org.mortbay.jetty.plugin.Jetty6RunMojo.execute(Jetty6RunMojo.java:184)
at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:101)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:209)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:153)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:145)
at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:84)
at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:59)
at org.apache.maven.lifecycle.internal.LifecycleStarter.singleThreadedBuild(LifecycleStarter.java:183)
at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:161)
at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:320)
at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:156)
at org.apache.maven.cli.MavenCli.execute(MavenCli.java:537)
at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:196)
at org.apache.maven.cli.MavenCli.main(MavenCli.java:141)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:290)
at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:230)
at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:409)
at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:352)
2012-09-19 10:26:37.178::WARN: Failed startup of context org.mortbay.jetty.plugin.Jetty6PluginWebAppContext@f8ff42{/JavaServerFaces,C:\workarea\developme
nt\JavaServerFaces\src\main\webapp}
java.lang.RuntimeException: java.lang.LinkageError: loader constraint violation: loader (instance of org/codehaus/plexus/classworlds/realm/ClassRealm) pre
viously initiated loading for a different type with name "javax/el/ExpressionFactory"
at com.sun.faces.config.ConfigureListener.contextInitialized(ConfigureListener.java:292)
at org.mortbay.jetty.handler.ContextHandler.startContext(ContextHandler.java:540)
at org.mortbay.jetty.servlet.Context.startContext(Context.java:135)
at org.mortbay.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1220)
at org.mortbay.jetty.handler.ContextHandler.doStart(ContextHandler.java:510)
at org.mortbay.jetty.webapp.WebAppContext.doStart(WebAppContext.java:448)
at org.mortbay.jetty.plugin.Jetty6PluginWebAppContext.doStart(Jetty6PluginWebAppContext.java:110)
at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:39)
at org.mortbay.jetty.handler.HandlerCollection.doStart(HandlerCollection.java:152)
at org.mortbay.jetty.handler.ContextHandlerCollection.doStart(ContextHandlerCollection.java:156)
at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:39)
at org.mortbay.jetty.handler.HandlerCollection.doStart(HandlerCollection.java:152)
at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:39)
at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:130)
at org.mortbay.jetty.Server.doStart(Server.java:222)
at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:39)
at org.mortbay.jetty.plugin.Jetty6PluginServer.start(Jetty6PluginServer.java:132)
at org.mortbay.jetty.plugin.AbstractJettyMojo.startJetty(AbstractJettyMojo.java:371)
at org.mortbay.jetty.plugin.AbstractJettyMojo.execute(AbstractJettyMojo.java:307)
at org.mortbay.jetty.plugin.AbstractJettyRunMojo.execute(AbstractJettyRunMojo.java:203)
at org.mortbay.jetty.plugin.Jetty6RunMojo.execute(Jetty6RunMojo.java:184)
at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:101)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:209)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:153)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:145)
at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:84)
at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:59)
at org.apache.maven.lifecycle.internal.LifecycleStarter.singleThreadedBuild(LifecycleStarter.java:183)
at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:161)
at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:320)
at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:156)
at org.apache.maven.cli.MavenCli.execute(MavenCli.java:537)
at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:196)
at org.apache.maven.cli.MavenCli.main(MavenCli.java:141)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:290)
at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:230)
at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:409)
at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:352)
Caused by: java.lang.LinkageError: loader constraint violation: loader (instance of org/codehaus/plexus/classworlds/realm/ClassRealm) previously initiated
loading for a different type with name "javax/el/ExpressionFactory"
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
at org.codehaus.plexus.classworlds.realm.ClassRealm.loadClassFromSelf(ClassRealm.java:386)
at org.codehaus.plexus.classworlds.strategy.SelfFirstStrategy.loadClass(SelfFirstStrategy.java:42)
at org.codehaus.plexus.classworlds.realm.ClassRealm.loadClass(ClassRealm.java:244)
at org.codehaus.plexus.classworlds.realm.ClassRealm.loadClass(ClassRealm.java:230)
at org.apache.jasper.runtime.JspApplicationContextImpl.getExpressionFactory(JspApplicationContextImpl.java:80)
at com.sun.faces.config.ConfigureListener.registerELResolverAndListenerWithJsp(ConfigureListener.java:693)
at com.sun.faces.config.ConfigureListener.contextInitialized(ConfigureListener.java:243)
… 41 more
2012-09-19 10:26:37.194::INFO: Started SelectChannelConnector@0.0.0.0:8080[/java]
Quick Fix
In the pom.xml, add the following block of dependency:
[xml] <dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>[/xml]
Proxying without AOP
Case
You have many operations to execute on each method call. At first glance, this is the perfect case to write an AOP mechanism (such as in this example Transaction Management with Spring in AOP).
Anyway, sometimes AOP won’t work, for instance when OSGi jars and their inherent opacity prevent you from cutting method calls.
Yet, I suggest here a workaround. In the following example, we log each method call, with inputs and outputs (returned values ; you can improve the code sample to handle raised exceptions, too).
Solution
Starting Point
Let’s consider an interface MyServiceInterface
. It is actually implemented by MyServiceLogic
.
An EJB MyServiceBean
has a field of type MyServiceInterface
, and the concrete implementation is of type MyServiceLogic
.
Without proxying nor AOP, the EJB should look like:
[java]
public MyServiceBean extends … implements …{
private MyServiceInterface myServiceLogic;
public MyServiceBean() {
this.myServiceLogic = new MyServiceLogic();
}
} [/java]
We have to insert a proxy in this piece of code.
Generic Code
The following piece of code is technical and generic, which means it can be used in any business context. We use the class InvocationHandler
, that is part of package java.lang.reflect
since JDK 1.4
(in order to keep the code light, we don’t handle the exceptions ; consider adding them as an exercise 😉 )
[java]public class GenericInvocationHandler<T> implements InvocationHandler {
private static final String NULL = "<null>";
private static final Logger LOGGER = Logger.getLogger(GenericInvocationHandler.class);
private final T invocable;
public GenericInvocationHandler(T _invocable) {
this.invocable = _invocable;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
final Object answer;
LOGGER.info(">>> " + invocable.getClass().getSimpleName() + "." + method.getName() + " was called with args: " + arrayToString(args));
answer = method.invoke(invocable, args);
// TODO handle throwables
if (method.getReturnType().equals(Void.class)) {
LOGGER.info("<<< (was a void method) ");
} else {
LOGGER.info("<<< " + invocable.getClass().getSimpleName() + "." + method.getName() + " returns: " + (answer == null ? "<null>" : answer.toString()));
}
return answer;
}
private static String arrayToString(Object… args) {
final StringBuilder stringBuilder;
stringBuilder = new StringBuilder();
for (Object o : args) {
stringBuilder.append(null == o ? NULL : o.toString());
}
return stringBuilder.toString();
}
}[/java]
Specific Code
Let’s return to our business requirement. The EJB has to be modified, and should be like:
[java]
public MyServiceBean extends … implements …{
private MyServiceInterface myServiceLogic;
public MyServiceBean() {
final MyServiceInterface proxied;
proxied = new MyServiceLogic();
this.myServiceLogic = (MyServiceInterface) Proxy.newProxyInstance(proxied.getClass().getClassLoader(),
proxied.getClass().getInterfaces(),
new GenericInvocationHandler<MyServiceInterface>(proxied));
}[/java]
From now and then, all the methods calls will be logged… All that without AOP!