Saturday, April 7, 2012

Class Reloading-4 using Struts2 OSGi plugin without springs dependency

Class Reloading-4 using Struts2 OSGi plugin without springs dependency

I am writing this blog because I did not find a tutorial of using struts2 OSGi plugin without spring dependency. The official plugin document is a bit blurred. In fact this plugin has very minimal documentation. Which shows either people found it uninteresting or are unknown to this plugin. So in order to bring some interest to this plugin and one can easily get a feel of how this works, I am writing this along with small projects in google code, which should be easy to run as soon as you checkout. These are eclipse projects and maven managed dependencies.



The main project already contains 3 bundles. 
MyOsgi.jar
struts2-osgi-admin-bundle-2.3.1.jar
struts2-osgi-demo-bundle-2.3.1.jar (The official demo bundle comes with spring dependency injection for ActionClass creation, which I changed to a normal action without DI, it works great) 
This is my web.xml. Note the TemplatePath variable I found this out the hard way.


<web-app id="struts_blank" version="2.4" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns="http://java.sun.com/xml/ns/j2ee" 
xsi:schemalocation=
"http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <display-name>Struts Blank Convention</display-name>

    <listener>
        <listener-class>view.InitListener</listener-class>
    </listener>

 <context-param>
     <param-name>TemplatePath</param-name>
     <param-value>class://</param-value>
 </context-param>
 <context-param>
     <param-name>struts.osgi.clearBundleCache</param-name>
     <param-value>false</param-value>
 </context-param>
 
 <listener>
        <listener-class>
           org.apache.struts2.osgi.StrutsOsgiListener
        </listener-class>
    </listener>
 
    <filter>
        <filter-name>struts2</filter-name>
        <filter-class>
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
        </filter-class>
    </filter>

    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
      

    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

Here is my struts.properties
struts.ObjectFactory tells that class reloading and instantiation is done by OSGi Note my second line is committed

struts.objectFactory=osgi
#struts.objectFactory.delegate=sprintOsgi

All the dependencies are managed by maven. So here is the pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>view</groupId>
    <artifactId>osgi</artifactId>
    <version>${project.version}</version>
    <packaging>war</packaging>
    <name>Struts 2 Blank Convention Webapp</name>

    <properties>
        <project.version>2.3.1</project.version>
        <struts2.version>${project.version}</struts2.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.apache.struts</groupId>
            <artifactId>struts2-core</artifactId>
            <version>${struts2.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.struts</groupId>
            <artifactId>struts2-convention-plugin</artifactId>
            <version>${struts2.version}</version>
        </dependency>

         

        <dependency>
          <groupId>org.apache.struts</groupId>
          <artifactId>struts2-config-browser-plugin</artifactId>
          <version>${struts2.version}</version>
        </dependency>

        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.5</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.4</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.0</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
      <groupId>velocity</groupId>
      <artifactId>velocity</artifactId>
      <version>1.5</version>
  </dependency>
  
  <dependency>
      <groupId>velocity-tools</groupId>
      <artifactId>velocity-tools</artifactId>
      <version>1.3</version>
  </dependency>
  

  <dependency>
   <groupId>commons-digester</groupId>
   <artifactId>commons-digester</artifactId>
   <version>2.1</version>
  </dependency>
  <dependency>
   <groupId>org.apache.struts</groupId>
   <artifactId>struts2-osgi-plugin</artifactId>
   <version>${struts2.version}</version>
  </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <encoding>UTF-8</encoding>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.mortbay.jetty</groupId>
                <artifactId>maven-jetty-plugin</artifactId>
                <version>6.1.21</version>
                <configuration>
                    <scanIntervalSeconds>10</scanIntervalSeconds>
                    <scanTargets>
                        <scanTarget>src/main/webapp/WEB-INF</scanTarget>
                        <scanTarget>src/main/webapp/WEB-INF/web.xml</scanTarget>
                        <scanTarget>src/main/resources/struts.xml</scanTarget>
                    </scanTargets>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>


If you know struts2 this should get you started. The project directory hierarchy looks like this
osgi
│   .classpath
│   .project
│   pom.xml
│
├───.settings
│
├───src
│   ├───main
│   │   ├───java
│   │   │   └───view
│   │   │       │   InitListener.java
│   │   │       │
│   │   │       └───actions
│   │   │               HelloAction.java
│   │   │
│   │   ├───resources
│   │   │   │   log4j.properties
│   │   │   │   struts.properties
│   │   │   │
│   │   │   └───view
│   │   │           package.properties
│   │   │           package_es.properties
│   │   │
│   │   └───webapp
│   │       │   index.jsp
│   │       │
│   │       └───WEB-INF
│   │           │   appengine-web.xml
│   │           │   web.xml
│   │           │
│   │           ├───classes
│   │           │   └───bundles
│   │           │       └───2
│   │           │               MyOsgi.jar
│   │           │               struts2-osgi-admin-bundle-2.3.1.jar
│   │           │               struts2-osgi-demo-bundle-2.3.1.jar
│   │           │
│   │           └───content
│   │                   hello.jsp
│   │
│   └───test
│       └───java
│           └───view
│               └───actions
└───target

This is really a struts-blank created using mvn archetype:generate for struts2 conventions example. The Action class HelloAction.class is actually empty and not really of any use. The interesting part is the folder where you can put all your bundles WEB-INF/classes/bundles/2 Just copy the bundle there and fire up the browser http://localhost:6060/osgi/osgi/admin/


You will see the below screen:



There is the shell for apache felix: http://localhost:6060/osgi/osgi/admin/shell.action Click on that, open the shell and type ps It will show all the active bundles
$ ps
START LEVEL 3

   ID   State         Level  Name

[   0] [Active     ] [    0] System Bundle (1.4.1)

[   1] [Active     ] [    1] Apache Felix Shell Service (1.0.2)

[   2] [Active     ] [    1] Apache Felix Framework (1.4.1)

[   3] [Active     ] [    2] MyOsgi (1.0.0.qualifier)

[   4] [Active     ] [    2] Struts 2 OSGi Admin Bundle (2.3.1)

[  24] [Active     ] [    2] Struts 2 OSGi Demo Bundle (2.3.1)


$ stop 3



$ uninstall 3





The number of running processed now becomes MyOsgi.
$ ps


START LEVEL 3

   ID   State         Level  Name

[   0] [Active     ] [    0] System Bundle (1.4.1)

[   1] [Active     ] [    1] Apache Felix Shell Service (1.0.2)

[   2] [Active     ] [    1] Apache Felix Framework (1.4.1)

[   4] [Active     ] [    2] Struts 2 OSGi Admin Bundle (2.3.1)

[  24] [Active     ] [    2] Struts 2 OSGi Demo Bundle (2.3.1)



The MyOSGI bundle will be now installed again:
---$ install file:///C:/Users/Samarjit/Desktop/test/MyOSGI/MyOsgi.jar
Bundle ID: 26



$ ps
START LEVEL 3

   ID   State         Level  Name

[   0] [Active     ] [    0] System Bundle (1.4.1)

[   1] [Active     ] [    1] Apache Felix Shell Service (1.0.2)

[   2] [Active     ] [    1] Apache Felix Framework (1.4.1)

[   4] [Active     ] [    2] Struts 2 OSGi Admin Bundle (2.3.1)

[  24] [Active     ] [    2] Struts 2 OSGi Demo Bundle (2.3.1)

[  26] [
Installed
  ] [    1] MyOsgi (1.0.0.qualifier)



$ start 26




$ ps
START LEVEL 3

   ID   State         Level  Name

[   0] [Active     ] [    0] System Bundle (1.4.1)

[   1] [Active     ] [    1] Apache Felix Shell Service (1.0.2)

[   2] [Active     ] [    1] Apache Felix Framework (1.4.1)

[   4] [Active     ] [    2] Struts 2 OSGi Admin Bundle (2.3.1)

[  24] [Active     ] [    2] Struts 2 OSGi Demo Bundle (2.3.1)

[  26] [
Active     
] [    1] MyOsgi (1.0.0.qualifier)
Also look at eclipse console: Among other things you will also see the initiation logs of the MyOsgi bundle which is in our case a simple "Hello World!!". Go ahead and change that and uninstall/install again.

Creating Bundle MyOsgi.jar

We will now see how the bundles are created. Frankly enough I am really a new to OSGi, but I will try to create a very basic bundle, and this can be dropped in bundles folder and installed and activated. The MyOsgi project is ofcourse a basic ecclipse plugin project, created by
eclipse->New project->Plugin Project As you know eclipse is build on top of equinox OSGi, but since we are going to use Apache Felix which is bundles with the Struts2 OSGi plugin, just select the targeted runtime as standard.
Enter a project name, and select Target Platform (this project is targeted to run with:): Standard

MyOsgi
│   .project
│   .classpath
│   build.properties
│
├───src
│   └───myosgi
│           Activator.java
│
├───.settings
│       org.eclipse.jdt.core.prefs
│       org.eclipse.pde.core.prefs
│
├───META-INF
│       MANIFEST.MF
│
└───bin
    └───myosgi
            Activator.class
The main entry point of any bundle is this class. This needs to be mentioned in META-INF/MANIFEST.MF file. All I have done is added in the System.out.println() there which gets printed when the bundle is activated or deactivated.
package myosgi;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

public class Activator implements BundleActivator {

 /*
  * (non-Javadoc)
  * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
  */
 public void start(BundleContext context) throws Exception {
  System.out.println("Hello World!!");
 }
 
 /*
  * (non-Javadoc)
  * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
  */
 public void stop(BundleContext context) throws Exception {
  System.out.println("Goodbye World!!");
 }

}
This is how the MANIFEST.MF file should look like, which eclipse will create automatically. To export import packages you will need to edit this file.
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: MyOsgi
Bundle-SymbolicName: MyOsgi
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: myosgi.Activator
Import-Package: org.osgi.framework;version="1.3.0"
Bundle-RequiredExecutionEnvironment: JavaSE-1.6


To create the MyOsgi.jar, export from eclipse as a normal jar, but make sure NOT to overwrite META-INF/MANIFEST.MF. Once that jar is exported you can extract it and check the MANIFEST.MF file remains as it is. That's all so far. Also note all the bundle packages that are present in You should be able to understand how to use struts2 osgi plugin, install uninstall bundles. In the project uploaded in google code, you can checkout create a few mode bundles and have fun.

Class reloading-3 using Apache commons-jci-fam

Apache commons-jci-fam

This is a popular apache project based on JSR http://commons.apache.org/jci/usage.html
This project JCI is a (java compiler interface) and FAM is (FilesystemAlterationMonitor)

 The most important parts of the code that we are going to use are as below. This does the trick of class reloading.
ReloadingClassLoader classloader = new ReloadingClassLoader(this.getClass().getClassLoader());
ReloadingListener listener = new ReloadingListener();

listener.addReloadNotificationListener(classloader);

FilesystemAlterationMonitor fam = new FilesystemAlterationMonitor();
fam.addListener(directory, listener);
fam.start();



It took me a while to set up their example which I downloaded. I don't remember where I downloaded the example sources from. But running it was interesting. All the examples ran without a worry. Except for this ServerPageServlet.java which required one tricky configuration which I found out the hard way.
Well the curious and excited ones just checkout project from SVN repo http://code.google.com/p/class-reloading-test/source/browse/#svn/trunk/samjci.
Configure web.xml
 
  <servlet>
        <servlet-name>samjciservlet</servlet-name>
        <servlet-class>org.apache.commons.jci.examples.serverpages.ServerPageServlet</servlet-class>
        <init-param>serverpagesDir</param-name>
        <param-value>jsptest</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
Excerpts from ServerPageServlet.java showing some important lines that helps in compilation and class reloading.
This defines the directory for monitoring of jsp files that will need to be loaded.
final File serverpagesDir = new File(getServletContext().getRealPath("/") + getInitParameter("serverpagesDir"));
This store class actually stores the class in byte array form.

final TransactionalResourceStore store = new TransactionalResourceStore(new MemoryResourceStore()) {
    public void onStart() {..}

    /**
    * Gets called soon after onStart() by compiler. Conpiler writes the compiled class bytes in the store.
    * Soon after this onStop() gets called in which the class bytes are picked up and
    * a new class instance is created
    */
    public void write(String pResourceName, byte[] pResourceData) {
     ...
    }

    public void onStop() {
          ....
          final Class clazz = classloader.loadClass(clazzName);

          if (!HttpServlet.class.isAssignableFrom(clazz)) {
                log(clazzName + " is not a servlet");
                continue;
          }
          // create new instance of jsp page
          final HttpServlet servlet = (HttpServlet) clazz.newInstance();
          ....
    }
    
}


Although compilation is out of scope for this blog, but just for the sake of completeness, I am writing about it here.
jspListener = new CompilingListener(new JavaCompilerFactory().createCompiler("eclipse"), store) {

            private final JspGenerator transformer = new JspGenerator();
            private final Map sources = new HashMap();
            private final Set resourceToCompile = new HashSet();

            public void onStart(FilesystemAlterationObserver pObserver) {
                super.onStart(pObserver);

                resourceToCompile.clear();
            }


            public void onFileChange(File pFile) {
                if (pFile.getName().endsWith(".ss")) {
                    final String resourceName = ConversionUtils.stripExtension(getSourceNameFromFile(observer, pFile)) + ".java";

                    log("Updating " + resourceName);
                    byte ar[] = transformer.generateJavaSource(resourceName, pFile);
                    sources.put(resourceName, ar);
                    System.out.println(new String(ar));
                    resourceToCompile.add(resourceName);
                }
                super.onFileChange(pFile);
            }


            public void onFileCreate(File pFile) {
                if (pFile.getName().endsWith(".ss")) {
                    final String resourceName = ConversionUtils.stripExtension(getSourceNameFromFile(observer, pFile)) + ".java";

                    log("Creating " + resourceName);

                    sources.put(resourceName, transformer.generateJavaSource(resourceName, pFile));

                    resourceToCompile.add(resourceName);
                }
                super.onFileCreate(pFile);
            }


            public String[] getResourcesToCompile(FilesystemAlterationObserver pObserver) {
                // we only want to compile the jsp pages
                final String[] resourceNames = new String[resourceToCompile.size()];
                resourceToCompile.toArray(resourceNames);
                return resourceNames;
            }


            public ResourceReader getReader( final FilesystemAlterationObserver pObserver ) {
                return new JspReader(sources, super.getReader(pObserver));
            }
        };
        jspListener.addReloadNotificationListener(classloader);
        
        fam = new FilesystemAlterationMonitor();
        fam.addListener(serverpagesDir, jspListener);
        fam.setInterval(3000);
        fam.start();
  

Whenever a new *.ss file gets created OnFileChange() or onFileCreate() gets called, which writes the resource names in resourceToCompile list. Super class of CompilationListener is ReloadingListener. This is part of ReloadingListener.java

public void onStop( final FilesystemAlterationObserver pObserver ) {
        
        
        if (store instanceof Transactional) {
            ((Transactional)store).onStart();
        }

        final boolean reload = isReloadRequired(pObserver);

        if (store instanceof Transactional) {
            ((Transactional)store).onStop();
        }
        
        if (reload) {
            notifyReloadNotificationListeners();
        }
        
        super.onStop(pObserver);
    }

The onStart() method actually just clears various flags of compiler and flags of stores. The real execution starts when onStop() gets called. onStop() method of ReloadingListener has the high level steps of compilation and followed by reload of classes.
It has one method final boolean reload = isReloadRequired(pObserver); which logs the changes resources
  log.debug("created:" + created.size() + " changed:" + changed.size() + " deleted:" + deleted.size() + " resources");, then it calls
  final String[] resourcesToCompile = getResourcesToCompile(pObserver);.
 After this actual compilation happens for each source that got changed
  final CompilationResult result = compiler.compile(resourcesToCompile, reader, transactionalStore); 
At the end it logs the errors and warnings of compilation
  log.debug(errors.length + " errors, " + warnings.length + " warnings"); Once back from isReloadingRequired(), it is sure that class compilation is complete and class bytes are available in the store. The only thing remains is the read those bytes and load them into a class. ....
The following log gets written automatically when the ss.ss file is edited using a notepad. Just hit Ctrl+S after modification and see the result.


created:0 changed:0 deleted:0 resources
check signal
check signal
onStart F:\eclipse\workspace\HTMLProcessor\deploypath\wtpwebapps\samjci\jsptest
onStart F:\eclipse\workspace\HTMLProcessor\deploypath\wtpwebapps\samjci\jsptest
onFileChange F:\eclipse\workspace\HTMLProcessor\deploypath\wtpwebapps\samjci\jsptest\ss.ss
onFileChange F:\eclipse\workspace\HTMLProcessor\deploypath\wtpwebapps\samjci\jsptest\ss.ss
Apr 08, 2012 1:33:14 AM org.apache.catalina.core.ApplicationContext log
INFO: samjciservlet: Updating ss.java
import java.io.PrintWriter;
import java.io.IOException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
public class ss extends HttpServlet {
  protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    final PrintWriter out = response.getWriter();
System.out.println("Custom jsp servlet:"+this.getClass().getName());    out.write("this is ss changed ");
    out.close();
    out.flush();
  }
}

onStop F:\eclipse\workspace\HTMLProcessor\deploypath\wtpwebapps\samjci\jsptest
onStop F:\eclipse\workspace\HTMLProcessor\deploypath\wtpwebapps\samjci\jsptest
created:0 changed:1 deleted:0 resources
created:0 changed:1 deleted:0 resources
1 classes to compile
1 classes to compile
className=ss
className=ss
fileName=ss.java
fileName=ss.java
typeName=ss
typeName=ss
compiling ss.java
...
writing resource ss.class(1314)
0 errors, 1 warnings
0 errors, 1 warnings
reading resource ss.class
reading resource ss.class
org.apache.commons.jci.stores.ResourceStoreClassLoader@18e3b6c[WebappClassLoader
  delegate: false
  repositories:
    /WEB-INF/classes/
----------> Parent Classloader:

reading resource ss.class
org.apache.commons.jci.stores.ResourceStoreClassLoader@18e3b6c[WebappClassLoader
  delegate: false
  repositories:
    /WEB-INF/classes/
----------> Parent Classloader:
org.apache.catalina.loader.StandardClassLoader@12c8b2a
] found class: ss (1314 bytes)
org.apache.commons.jci.stores.ResourceStoreClassLoader@18e3b6c[WebappClassLoader
  delegate: false
  repositories:
    /WEB-INF/classes/
----------> Parent Classloader:
org.apache.catalina.loader.StandardClassLoader@12c8b2a
] found class: ss (1314 bytes)
reading resource javax/servlet/http/HttpServlet.class
reading resource javax/servlet/http/HttpServlet.class
org.apache.commons.jci.stores.ResourceStoreClassLoader@18e3b6c[WebappClassLoader
  delegate: false
  repositories:
    /WEB-INF/classes/
----------> Parent Classloader:
org.apache.catalina.loader.StandardClassLoader@12c8b2a
] loaded from store: ss
org.apache.commons.jci.stores.ResourceStoreClassLoader@18e3b6c[WebappClassLoader
  delegate: false
  repositories:
    /WEB-INF/classes/
----------> Parent Classloader:
org.apache.catalina.loader.StandardClassLoader@12c8b2a
] loaded from store: ss
reading resource java/lang/Object.class
reading resource java/lang/Object.class
Apr 08, 2012 1:37:50 AM org.apache.catalina.core.ApplicationContext log
INFO: samjciservlet: Activating new map of servlets {ss=ss@cb70f0}
...
...
If the newly created and loaded servlet is loaded then the following log comes. The url invoked is http://localhost:6060/samjci/samjci/ss the new servlet getscalled. This line comes from the System.out.println() written dynamically in the servlet "Custom jsp servlet:ss".
request.getpathInfo:/ss
Custom jsp servlet:ss
Apr 08, 2012 1:56:22 AM org.apache.catalina.core.ApplicationContext log
INFO: samjciservlet: Request /samjci/samjci/ss
Apr 08, 2012 1:56:22 AM org.apache.catalina.core.ApplicationContext log
INFO: samjciservlet: Checking for serverpage ss
Apr 08, 2012 1:56:22 AM org.apache.catalina.core.ApplicationContext log
INFO: samjciservlet: Delegating request to ss

Now everything got cleared from the logs. Hope this inspires new ideas. You can find the project in google code SVN repository here http://code.google.com/p/class-reloading-test/source/browse/#svn/trunk/samjci

Class Reloading-2 using AgentSmith

Class Reloading using AgentSmith

AgentSmith uses jvm HotSwap method for class reloading. The project is simple and consists of a few classes. The application works by java instrumentation API. We will break down the working of each of the components in it and see how it all fits together to enable automatic class reloading.


Setup:

This is what worked for me. SVN checkout from the below repository. https://svn.java.net/svn/agentsmith~svn/tags/smith-1.0
Compile using
ant dist

This will create smith-1.0.jar file. This jar file needs to be used to instrument the jvm of tomcat which will be running the web application. The following code does exactly that:
Edit catalina.sh or catalina.bat to add the following to JAVA_OPTS variables.

-javaagent:E:/samarjit/tomcat-6.0.35/lib/smith-1.0.jar=classes=E:/samarjit/tomcat-6.0.35/webapps/sam-2.3.1/WEB-INF/classes,jars=E:/samarjit/tomcat-6.0.35/webapps/sam-2.3.1/WEB-INF/lib,period=1000,loglevel=INFO



If you start the tomcat now it will show the debug messages, which will show the path AgentSmith is looking for class changes E:/samarjit/tomcat-6.0.35/webapps/sam-2.3.1/WEB-INF/classes and library changes.in E:/samarjit/tomcat-6.0.35/webapps/sam-2.3.1/WEB-INF/lib.

To test if it really works deploy a webapplication in tomcat outside eclipse. Now recompile some class file after modifying some contents of a method in it. Then invoke that servlet. You can instantly see the effect.

Some excerpts form Smith.java

public static void premain(String agentArgs, Instrumentation inst) {
  System.out.println("Premain()");
  initialize(agentArgs, inst);
 }


The portion that starts off the monitor is here:

/**
  * Creates and starts a new Smith agent. Please note that periods smaller than
  * 500 (milliseconds) won't be considered.
  * 
  * @param inst
  *          the instrumentation implementation
  * @param args
  *          the {@link SmithArgs} instance
  */
 public Smith(Instrumentation inst, SmithArgs args) {
  this.inst = inst;
  this.classFolder = args.getClassFolder();
  this.jarFolder = args.getJarFolder();
  int monitorPeriod = MONITOR_PERIOD_MIN_VALUE;
  if (args.getPeriod() > monitorPeriod) {
   monitorPeriod = args.getPeriod();
  }
  log.setUseParentHandlers(false);
  log.setLevel(args.getLogLevel());
  ConsoleHandler consoleHandler = new ConsoleHandler();
  System.out.println("Log level:"+args.getLogLevel());
  consoleHandler.setLevel(args.getLogLevel());
  log.addHandler(consoleHandler);

  service = Executors.newScheduledThreadPool(2);

  FileMonitor fileMonitor = new FileMonitor(classFolder, "class");
  fileMonitor.addModifiedListener(this);
  service.scheduleWithFixedDelay(fileMonitor, 0, monitorPeriod,
    TimeUnit.MILLISECONDS);

  if (jarFolder != null) {
   JarMonitor jarMonitor = new JarMonitor(jarFolder);
   jarMonitor.addJarModifiedListener(this);
   service.scheduleWithFixedDelay(jarMonitor, 0,
                                        monitorPeriod,
     TimeUnit.MILLISECONDS);
  }

  log.info("Smith: watching class folder: " + classFolder);
  log.info("Smith: watching jars folder: " + jarFolder);
  log.info("Smith: period between checks (ms): " + monitorPeriod);
  log.info("Smith: log level: " + log.getLevel());
 }


The most important that does the HotSwap is here: There is a call to inst.redefineClasses(...) which does the trick.

/**
  * Redefines the specified class
  * 
  * @param className
  *          the class name to redefine
  * @param event
  *         the event which contains the info to access the modified class
  *          files
  * @throws IOException
  *           if the inputstream is someway unreadable
  * @throws ClassNotFoundException
  *           if the class name cannot be found
  * @throws UnmodifiableClassException
  *           if the class is unmodifiable
  */
 protected void redefineClass(String className, EventObject event) {
  Class[] loadedClasses = inst.getAllLoadedClasses();
  for (Class clazz : loadedClasses) {
   if (clazz.getName().equals(className)) {
    try {
       ClassDefinition definition = 
                            new ClassDefinition(clazz, getByteArrayOutOf(event));
       inst.redefineClasses( new ClassDefinition[] { definition });
       if (log.isLoggable(Level.FINE)) {
         log.log(Level.FINE, "Redefined: "                                                + clazz.getName());
       }
      } catch (Exception e) {
       log.log(Level.SEVERE, "error", e);
      }
   }
  }
 }


Once this class redefinition is done, the updated class automatically gets reflected when invoked. Till here all the changes done are framework agnostic. But if you are using a framework like struts, you will probably need to update the framework related configurations.

Thursday, April 5, 2012

Class Reloading-1 Webapp in java

Imagine writing java code hit Ctrl+S and then click refresh on browser, voila the changed java code actually works!! Or in server environment you made a slight change to a class file. Wish it were loaded without require of a server restart. Or you changed some resource properties file, did you imagine that your changed resource properties file will also require a server restart. It would be nice if all web applications worked this way out of box.

Well, there is a way to make web applications reload each time it detects a change in resources. There are two ways an app server can help in reloading changes.
  • Auto-reload (Reloads the changed files and classes and then re-initialize the application, which may take another 30sec to 1 min. All the servlets that load on startup are restarted, caches cleared and reloaded. This is almost takes the same time as restarting tomcat.)
  • Hot Swap of classes - Provided by JDK class redefining without class reloading, but this works only in debug mode Java Platform Debugger Architecture (JPDA) . This can be done in tomcat running inside eclipse or other IDE. This requires tomcat to run in debug mode in eclipse). This article http://blog.redfin.com/devblog/2009/09/how_to_set_up_hot_code_replacement_with_tomcat_and_eclipse.html describes the setup of doing this with eclipse and tomcat.
  • FastSwap (websphere) only available in WebLogic (http://docs.oracle.com/cd/E15051_01/wls/docs103/deployment/deployunits.html ). This is better than Hot Swap, it is not only limited to changes in the method body. It also does not user a new classloader. I am not sure how it does the trick, I have never used it also, but wish it was there in tomcat too :( .
But there are ways to reload only specific parts of an application in tomcat without disrupting the other applications. For the curious ones I would first list down some some frameworks and tools that does class reloading.
Tools that do class reloading:



Frameworks which helps or does class reloading:Grails, play framework, tapestry, spring scripted beans, struts2 by using struts-spring-plugin, OSGi, struts2 OSGi plugin.


Non Java frameworks that does class reloading: Ruby on Rails, PHP.

Other ways to achieve class reloading:JSP - Create the full application in JSP. Of course some would say mixing views with models with controller is not a good idea. But this problem has been solved in PHP world many times. But frankly speaking if JSPs can be compiled and loaded by tomcat, why cannot tomcat load other classes? I greatly feel app servers should provide this and hope it would not be too difficult.


Oracle's PL/SQL- Make the front end generic, everything in configuration files, and push all the logic including gui validation to PL/SQL. Believe me this is possible and I have seen it. PL/SQLs can be compiled anytime.


Class Reloading Methods

(disclaimer: I am not an expert in jvm, neither I am expert in class loaders. So whatever I am going to say is based on my research of few other blogs, inspection of code struts spring plugin which does class reloading.)
Most I have seen two ways of class reloading, which are using java (a) HotSwap or (b) java ClassLoaders.
The main problems that makes class reloading hard in jvm level are bytecode optimisers and jvm JIT compiler and multi generation garbage collection. I will not go too much deep into jvm internals but I would certainly point to a blog which explains about class reloading in webapps, OSGi in general http://zeroturnaround.com/blog/rjc301/. This is another blog which discusses about why jvm hotswap is limited only to method bodies modification http://zeroturnaround.com/blog/reloading_java_classes_401_hotswap_jrebel/, this blog has a really inspiring video "A video presentation 'Do you really get class loaders' by Jevgeni Kabanov followed by a nice example of the very basic class reloading in action.

While implementing the various class reloading methods I faced some issues so I intend to show the complete setup here so that you can get a feel about it.


2. Fakereplace- Using fakereplace in a webapp is really simple. Just add the following to JAVA_OPTS in catalina.sh or catalina.bat if you are running tomcat. -javaagent:C:/Users/Samarjit/Desktop/fakereplace-parent-0.9-bin/fakereplace/fakereplace.jar -Dorg.fakereplace.packages=view
3. Simple Class Reloading - as explained by Jevgeni Kabanov in the above blog. I just created a small project in google code, which you can directly checkout and see it in action. URL: http://code.google.com/p/class-reloading-test/source/browse/#svn/trunk/ClassLoaderTest
4. Struts2 Spring plugin with class reloading- This is simple and words well out of box as per instructions. I figured out that it is using apache-commons-jci-fam. But they have integrated with springs so tightly that you will not get who is doing the trick of class reloading. After digging deep into it I decided to give apache-commons-jci-fam a try without even a sprinkle of spring. That makes my next project
5. Apache-commons-fam-jci - They have examples but those requires a bit of configuration to get them running. So I created a project that can be just checked out and run. They simulate JSP container. It reads JSP from plain text HTML then created a java source of it and compiles and finally loads the class, all of this happens on the fly and you do not need to restart your servlet container. If they can work with JSP we can apply the same to normal java classes and servlets too. URL: http://classreloadingwebapp.blogspot.com/2012/04/class-reloading-using-apache-commons.html
5. Struts2 OSGi plugin - Again this plugin seems not to be used by anyone. As there are hardly any documentation, also the plugins page does not show how to implement it with and without springs. Somehow its mixed up in the document. So I created a project completely without springs. Where you can get a feel of just the OSGi plugin. URL: http://classreloadingwebapp.blogspot.com/2012/04/class-reloading-4-using-struts2-osgi.html