Thursday, March 31, 2005

javac's "-source" changes your class behavior ???

I came across this yesterday. Very wired!

I took quite time to understand what was going wrong. My JUnit tests worked fine in IDEA but they were failing when run from command line with ANT. Then I figured out that the only difference was the language compatibility level set in IDEA to "1.4" and ommited (set do default) in ANT.

Below is an example that you can try. I work on WinXP, Java HotSpot(TM) Client VM (build 1.4.2_06-b03, mixed mode).

public class ShowSourceImpact {

public static void main(String[] args) {
final Integer val = new Integer(1);
Sample s = new Sample() {
Integer createI() {
return val;
}
};

System.out.println("'i' is: " + s.getI());
System.out.println("'i' should be: " + val);
}

static class Sample {
private Integer i = null;

public Sample() {
i = createI();
}

Integer createI() {
return new Integer(-1);
}

public Integer getI() {
return i;
}
}
}

Then, when you compile it like javac ShowSourceImpact.java and run it you'll see:
'i' is: null
'i' should be: 1

When you compile it with javac -source 1.4 ShowSourceImpact.java you'll get the following:
'i' is: 1
'i' should be: 1

So the problem is when you have an inner class and you overwrite its methods in such a way that they rely on final variables declared in the outer method. See createI() method in the example above.

For "1.3" source code it seems that the value of these final variables are null inside the inner class. This works ok with "1.4" source.

I thought that the "-source" 1.3 vs 1.4 vs 1.5 was only about the new keywords introduced to the language. However with the example above it looks there is more to that.

Anyone has any good explanation for that?

Tuesday, March 22, 2005

MDB+Struts+Spring+Hibernate - the WebLogic way

Pretty heavy - the title. And the post is pretty long too. But I must say it was quite a challenge to put all these pieces together.

Struts+Spring+Hibernate is not exceptional if not the fact that I didn't find any good info on how to use Message-Driven Beans with Spring. It seems that MDBs remain the only thing for which Spring doesn't have an answer. If you need asynchronous, guaranteed delivery calls, you'll need MDB.

Spring contains abstract classes that should help you to develop your MDB. There is one problem though. Let's say you want your MDBs to rely on your application functionality that you assembled together in your main "applicationContext.xml". Let's say also that you want to have a pool 3 MDBs that can grow up to 20.

If you follow the Spring documentation you'll end up with, at the application startup, 3 duplicate contexts because each bean instance will load its own, separate context. For a small app this is not a problem. For a bigger application it may become one. But don't worry - there is a solution to that. It's called ContextSingletonBeanFactoryLocator.

But wait, it's not over yet.

Now imagine that your presentation layer relies on your core functionality captured in the same "applicationContext.xml". Ahh, I forgot to say - your requirement is to use Struts for MVC piece. And you want Hibernate to be lazy in all layers (load associated objects on demand only). And last, but not least, this all should run on WebLogic Server.

Below are all the ingredients that I used to make the whole thing work. Note that I'm a Spring newbie and I'm pretty sure there must be an easier way to do this. Looking forward for your comments.

If you want to continue be prepared for: classloaders' hell, Spring hacking, more than one Spring beans XML file ... But you'll be awarded - at the end it all seems to work fine together!!!

EAR layout is as follows:

  • APP-INF/lib - Hibernate and Spring jars; a jar (myCore.jar) file with all my objects except the presentation piece and MDBs
  • META-INF - usual .ear descriptors
  • myMdbs.jar - EJB jar file containing my message-driven beans
  • myWeb.war - my presentation layer classes plus Struts
"APP-INF/lib" is a special WebLogic directory where you can put all supporting libraries and all your classes that should be shared by your EJBs and WAR elements. From what I understood, the classloader for this directory is a parent classloader for the EJB classloader. And EJB classloader is parent for WAR classloader. This means that any class that is loaded by APP-INF/lib classloader cannot reference any other class that is present either in WAR/WEB-INF/lib or EJB. See where I'm heading? Ok, I'll give you a hint - OpenSessionInViewFilter defined in spring-orm.jar. More details in a second.

The first implication of the classloader hierarchy is that you cannot use "spring.jar" and just drop it to "APP-INF/lib". Your "APP-INF/lib" will contain "spring-(core,aop,context,dao,orm).jar" and "spring-web.jar" must go to WAR's "WEB-INF/lib".

Message-driven beans inheriting from "AbstractJmsMessageDrivenBean" can specify, in "ejb-jar.xml" file, an "env-entry" to point to the "applicationContext.xml" file. But as I said, using this approach and having several MDBs, I ended up with several copies of the application context loaded. ContextSingletonBeanFactoryLocator is the rescue here. I had to overwrite the ejbCreate() as follows:
public void ejbCreate() {
setBeanFactoryLocatorKey("MyApp");
setBeanFactoryLocator(ContextSingletonBeanFactoryLocator.getInstance());
super.ejbCreate();
}

In this case Spring will look for XML context file using the following resource path "classpath:beanRefContext.xml". In my case this file is located in "myCore.jar" file and contains only a single bean called "MyApp" that defines the real context:
<beans>
<bean id="MyApp" class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg>
<list><value>applicationContext.xml</value></list>
</constructor-arg>
</bean>
</beans>

To sum up, now I have Spring application context shared by all my MDBs.

Hibernate laziness in the view requires to use OpenSessionInViewFilter. In order to use this filter you have to also use ContextLoaderListener, as the filter relies on a web application context set as ServletContext's attribute (webapp application scope). In the web tier I also wanted to use (share) the same Spring application context as the one used by my MDBs. To achieve that, in addition to the normal listener declaration, you need to define some "context-param" in your web.xml file:
<context-param>
<param-name>locatorFactorySelector</param-name>
<param-value>classpath:beanRefContext.xml</param-value>
</context-param>
<context-param>
<param-name>parentContextKey</param-name>
<param-value>MyApp</param-value>
</context-param>

You think "ouf, almost there..."? Well, not quite yet. You try to run this whole thing and you're getting a misterious "NoClassDefFoundError: org.springframework.web.filter.OncePerRequestFilter" (or maybe it was an ...Exception, don't remember). Then you look into your "web.xml" file - "But I don't use OncePerRequestFilter, so what the hell is going on?".

Well, the problem is that OpenSessionInViewFilter is part of "spring-orm.jar" - located in "APP-INF/lib" directory and loaded with the parent classloader to rule them all. But this class extends OncePerRequestFilter being part of "spring-web.jar" located in WAR's "/WEB-INF/lib" directory. As parent classloader cannot load classes available only to its child classloaders it seems like no way out. And I didn't find any, apart from hacking Spring jars by moving this filter class from "spring-orm.jar" to "spring-web.jar" - and I believe that's where it should be anyway.

There is one final problem. Even if you don't have any beans defined yet in the presentation layer, ContextLoaderListener will not start if it doesn't have "/WEB-INF/applicationContext.xml" file. This file can be empty (i.e. <beans></beans>) but it must be there.

Struts integration is actually pretty nice. You can find more info here. As I have to use a custom request processor I opted for DelegatingActionProxy approach. I hoped to put my action IoC config in the "/WEB-INF/applicationContext.xml" file so this file could become useful, but it seems that DelegatingActionProxy relies on web application context bound as another ServletContext attribute (different name than the context bound by ContextLoaderListener). This one is set by ContextLoaderPlugIn defined in "struts-config.xml" file:
<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
<set-property property="contextConfigLocation" value="/WEB-INF/strutsContext.xml" />
</plug-in>

The "stutsContext.xml" file contains my actions' IoC configuration.

... and that's it. Congratulations! You accomplished your mission successfully!!!

To sum up the main issues that I found were:
  • Hibernate filter class is defined in "spring-orm.jar" but I believe it should be defined in some other jar belonging to the web tier.
  • I probably still don't understand the Spring application contexts relationships and the use of ContextSingletonBeanFactoryLoader but for me there were too many bean files (3 and 1 empty) while I only really needed 2 - one for my business and DAO layer, and one for presentation layer.
  • From the Spring reference doc I understood that whether you use ContextLoaderFilter or ContextLoaderPlugIn for your Struts integration shouldn't really matter. I didn't managed to get rid of the the plug-in though.
  • I didn't find a good explanation of the use of ContextSingletonBeanFactoryLocator - it was more like debug and reverse engineering type of work to get it working

The most important though is that at the end it all worked smoothly toghether.

As I said at the beginning - I'm a Spring rookie, so please advise on how to put together all the components mentioned here but in a simpler way.

Monday, March 21, 2005

let's get it started!

Yep. Here it is. My first post. I'm a Java developer and this blog will be about what I know about Java and, more important, what I don't. I learn a lot from others' blogs. Nothing starts better a day than a good read at JavaBlogs. "So many questions, so little time..." It takes time to get answers if you don't know where to look or if you have nobody to ask ;-) I hope this blog will help me to get those answers. And you never know, maybe others will find useful information on this blog too.