Monday, February 15, 2010

Hotswap This!

Update: February 18, 2010

I just fixed the last two configuration issues and can now hotswap most classes, jsps, and other resources for both OpenMRS core and OpenMRS modules! See the instructions below.

Update: February 17, 2010

This is AWESOME! With the JRebel configuration (see below), I am now able to hotswap the following resources:
  • web classes (e.g. modify existing controllers)
  • api classes (e.g. create a new api class called Regimen)
  • JSPs under WEB-INF (e.g. add or modify
  • Controllers (e.g. create annotated controllers and JSPs)
  • messages.properties (e.g. change i18n properties on the fly)
  • Spring-managed objects get reloaded on the fly when you change any classes referenced in spring configuration files
  • module classes
  • module web resources like JSPs
I'm still in the process of configuring/testing whether changes to the following resources:
  • openmrs-servlet.xml (does not seem to hotswap these changes?)
  • web.xml (does not seem to hotswap these changes by default?)
  • applicationContext-*.xml (?)
  • dwr.xml (?)
  • hibernate mapping files (supposed to be available in JRebel 3.0)



Update: February 15, 2010

This is still a work in progress, but I've managed to configure JRebel, Tomcat, and Eclipse to hotswap classes within the core openmrs webapp as well as the reporting module. This took a bit of work because of our custom classloaders and module framework as well as the fact that we've adopted some pretty unconventional mechanisms within our current Ant build process and project structure.



Overview
Here's a brief summary of how JRebel works taken from the Zero Turnaround website:
How does JRebel work?
JRebel maps your project workspace directly to your running application. When you change any class or resource in your IDE, you can immediately see the change in your application, skipping the build and redeploy phases and reducing the 3-7 weeks your team wastes each year.
You can simply follow the instructions on the JRebel blog or the Reference Manaul included in the JRebel installation zipfile. Here are a few of the resources that were used throughout the process:
Here's the basic procedure that we need to follow:
  1. Install JRebel
  2. Install the JRebel Eclipse plugin (optional)
  3. Configure Eclipse
  4. Configure Tomcat
  5. Configure your application
Step 1 - Install JRebel
Follow the instructions from the HOWTO guide above. I installed JRebel to /var/lib/jrebel.

Step 2 - Install the JRebel Eclipse
Again, just follow the instructions in the HOWTO Guide.

Step 3 - Configure Eclipse
Simply set the path to the JRebel JAR under Window > Preferences > JRebel to /var/lib/jrebel/jrebel.jar.

Step 4 - Configure Tomcat
If you're running Tomcat on Windows/Mac you need to figure out how to add command-line options to the Tomcat start script or Tomcat Manager utility that eventually calls java to start the Tomcat threads. For Ubuntu users, locate the tomcat startup script (e.g. /etc/init.d/tomcat5.5) and add the following -noverify and -javaagent options to the script's JAVA_OPTS value.

I actually added the following three options to JAVA_OPTS:

-Xverify:none
I still don't have a very clear idea of what this option does, but it seems to disable class verification and might have something to do with modification to constructors (see the Troubleshooting section below).

-javaagent:/var/lib/jrebel/jrebel.jar=org.openmrs.util.OpenmrsClassLoader,org.openmrs.module.ModuleClassLoader
The java agent option is required to configure the JRebel JAR that is responsible for monitoring/reloading files when they are modified. Note that we had to add the OpenmrsClassLoader and ModuleClassLoader classes to let JRebel know where to locate/reload classes.

-Drebel.log=true
Included for debugging purposes only.

Your JAVA_OPTS variable should/might look similar to mine (displayed below):

JAVA_OPTS="-noverify -javaagent:/var/lib/jrebel/jrebel.jar=org.openmrs.util.OpenmrsClassLoader,org.openmrs.module.ModuleClassLoader -Drebel.log=true $JAVA_OPTS -Xmx512m -Xms512m -XX:PermSize=256m -XX:MaxPermSize=256m -XX:NewSize=128m -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5000"
More options can be found in the JRebel Configuration Guide.

Step 5 - Configure your application
Now here's the part that needs to be Create a rebel.xml file and place this under TOMCAT_HOME/webapps/openmrs/WEB-INF/classes. Eventually, this file could be included within the OpenMRS source and deployed within the WAR file on it's initial deployment, but unfortunately our WEB-INF source directory is a little "crazy" at the moment and getting the file into WEB-INF/classes would require some changes to the Ant build.xml file. For now, it would be better if we required developers to configure JRebel on their own.

rebel.xml

<?xml version="1.0" encoding="UTF-8"?>
<application
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.zeroturnaround.com"
xsi:schemaLocation="http://www.zeroturnaround.com http://www.zeroturnaround.com/alderaan/rebel-2_0.xsd">
<classpath>
<dir name="/home/jmiranda/Workspace/openmrs-1.6.x-trunk/build"></dir>
<dir name="/home/jmiranda/Workspace/module-reporting-trunk/build"></dir>
<dir name="/home/jmiranda/Workspace/module-serialization.xstream/build"></dir>
<dir name="/home/jmiranda/Workspace/module-htmlwidgets/build"></dir>
</classpath>
<web>
<link target="WEB-INF/">
<dir name="/home/jmiranda/Workspace/openmrs-1.6.x-trunk/web/WEB-INF">
<exclude name="**/*.xml"/>
</dir>
</link>
</web>
<web>
<link target="WEB-INF/view/module/reporting/">
<dir name="/home/jmiranda/Workspace/module-reporting-trunk/web/module">
<exclude name="**/*.xml"/>
</dir>
</link>
</web>
</application>

Step 6 - Restart Tomcat
Any time you make a change to the rebel.xml file, you should restart Tomcat. For me, after playing around with JRebel and the rebel.xml file for a little while, hotswapping of web classes started working. Now I need to test whether the service layer is

Next Steps
  • Configure with Eclipse IDE using WTP Server Integration with Tomcat, Glassfish

Troubleshooting
I ran into a couple of issues while working on this little project. These are all sort of fixed at this point, but I'm not entirely thrilled with the solutions so suggestions would be much appreciated.
  • [SOLVED] Invalid option -noverify
  • [SOLVED] JspRemovedException
  • [WORKAROUND] I/O failure during classpath scanning
  • [SOLVED] Unable to refresh JSPs on save
  • [SOLVED] BeanCurrentlyInCreationException: Error creating bean with name 'sessionFactory'

[SOLVED] Invalid option -noverify
Problem
Unfortunately, when I include the -noverify option within the JAVA_OPTS variable, I get an error (see below) during Tomcat 5.5 startup. I
need to do a little more research on this one, but for now I just
remove the -noverify option.

Invalid option -noverify
Cannot parse command line arguments
Workaround
Remove the -noverify option. However, when starting Tomcat 5.5 with the "-noverify" option removed, I get the following warning in the JRebel log. I can live with not being able to hotswap classes that have changes to their constructor, but if anyone can figure this out, please let me know. I'm hoping this does not prevent hotswapping of newly defined classes.
JRebel: '-noverify' missing, changing/adding/removing constructors will not be enabled!
Solution
Thanks to Jevgeni (from ZeroTurnaround - it's even true with blog comments) we resolved this issue by using the alternative syntax (Xverify:none).

[SOLVED] JspRemovedException
Problem

ERROR - ApplicationDispatcher.invoke(711) |2010-02-13 12:33:48,206| Servlet.service() for servlet jsp threw exception
org.zeroturnaround.javarebel.integration.jasper.JspRemovedException: /WEB-INF/view/module/reporting/reports/manageReportDesigns.jsp
at org.apache.jasper.compiler.Compiler.isOutDated(Compiler.java)
at org.apache.jasper.compiler.Compiler.isOutDated(Compiler.java:333)
at org.apache.jasper.JspCompilationContext.compile(JspCompilationContext.java:561)
...

Solution
I believe this was before I added support for the /openmrs/module/reporting "web" resources to the JRebel configuration. This exception was probably caused by the fact that JRebel was resolving all JSP/class files to the /openmrs "web". Therefore, by adding in another "web" element for the reporting module, JRebel is able to resolve the module's files correctly. However, this means that we need to manually add a "web" element for each new module that we deploy. Not a big deal, but it would be nice to find a way to include a rebel.xml within each module and allow that file to be picked up by JRebel at deployment time.

[WORKAROUND] FileNotFoundException: I/O failure during classpath scanning

Problem

ERROR - ContextLoader.initWebApplicationContext(215) |2010-02-13 02:40:39,394| Context initialization failed
org.springframework.beans.factory.BeanDefinitionStoreException: I/O failure during classpath scanning; nested exception is java.io.FileNotFoundException: class path resource [org/springframework/test/context
/support/AbstractTestExecutionListener.class] cannot be opened because it does not exist
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.findCandidateComponents(ClassPathScanningCandidateComponentProvider.java:222)
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:201)
...

Solution (temporary)
I temporarily fixed this by adding the spring-test.jar to TOMCAT_HOME/webapps/openmrs/WEB-INF/lib.

[SOLVED] Unable to refresh JSPs on save
Problem
This would appear to be the easiest thing to get working, but I've only spent a few minutes trying to configure it to work. We can still use the deploy-web target within our Ant build.xml for the time being, but it would awesome to get all classes/web resources being hotswapped when they are modified within Eclipse.

Solution
This was solved by making sure to specify the correct web > link target in the rebel.xml configuration. I was incorrectly using the webapp context (e.g. /openmrs) instead of the webapp root config directory (e.g. WEB-INF). I still need to make sure to incorporate all resources above WEB-INF including openmrs.css, openmrs.js, and others.

[SOLVED] BeanCurrentlyInCreationException: Error creating bean with name 'sessionFactory'
Problem

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'hL7ServiceTarget' defined in class path resource [applicationContext-service.xml]: Cannot resolve reference to bean 'hL7DAO' while setting bean property 'HL7DAO'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'hL7DAO' defined in class path resource [applicationContext-service.xml]: Cannot resolve reference to bean 'sessionFactory' while setting bean property 'sessionFactory'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'sessionFactory': FactoryBean which is currently in creation returned null from getObject

Solution
This was solved by adding in an exclude element into the web > link > dir compoment. This prevents the Spring configuration files from being read multiple times (JRebel already appears to be handling it through its Spring plugin).

<web>
<link target="WEB-INF/">
<dir name="/home/jmiranda/Workspace/openmrs-1.6.x-trunk/web/WEB-INF">
<exclude name="**/*.xml"/>
</dir>
</link>
</web>

2 comments:

Jevgeni Kabanov said...

Try -Xverify:none instead of -noverify.

Justin Miranda said...

Worked like a charm - thanks!