© Jose MarĂ­a Arranz SantamarĂ­a v0.8.8 March 16, 2016

Because Java is also a dynamic language and Groovy can still be a dynamic language in a Java based environment.

What is RelProxy

RelProxy is:

  1. A simple Java and Groovy hot class reloader for Java and Groovy providing transparent compilation and class reload on the fly with no need of a bootstrap class loader agent and avoiding context reloading. Reloading happens only in memory. Class reloading can be used in development phase and optionally in production (if source code can be uploaded to production).

  2. A scripting environment to execute Java code snippets the same as a shell script. There is no new language, is Java compiled on the fly, code in the initial archive can call to other normal Java files. Optionally .class can be saved in a cache to provide the fastest "scripting" language of the world.

  3. Execution of Java code snippets in command line (no need of packaging into an archive).

  4. A simple shell to code, edit and execute code snippets in Java interactively.

  5. JSR 223 Scripting API implementation for "Java" as the target scripting language. You can embed and execute Java code as scripting into your Java program.

The origins of RelProxy

RelProxy was born as the result of the intent of providing a way to hot class reloading for the ItsNat Java web framework in development and optionally in production time.

ItsNat templates are pure X/HTML/SVG files with no logic executed in server, when a template is changed ItsNat automatically reloads the file and the new version is used in the next attempt of using the template. View logic is coded in Java by using Java W3C DOM APIs modifying ItsNat templates and finally generating the required markup in load time or JavaScript code as the result of AJAX events.

As said before, ItsNat provided hot reloading of markup templates, but view logic, coded in Java, remained too static, if you needed to change the view logic you needed to redeploy the web application, in the best case only the web application context is reloaded. Providing some hot class reloading of view logic (coded in Java or Groovy as we will see later) with no need of context reload (context reload can be quick or very slow depending of the size of the application and session can be lost) would dramatically increase the productivity of ItsNat in development and in an edge case, view logic changes would be possible in production.

Redeploying is not strange to Java web developers when you leave the world of JSPs, JSPs were designed to be hot reloaded including logic coded in Java, server centric frameworks usually provides some kind of hot reloaded templating, but in most of them conventional Java code is not hot reloaded unless context reload or redeploy.

The first attempt to provide hot reload of view logic to ItsNat was trying to use Groovy, many people think Groovy is an "interpreted" language and is not, Groovy is also a language compiled to bytecode, but compilation happens on demand, generated bytecode is usually more flexible ("dynamic") than conventional Java and hot class reloading is built-in. The problem of using Groovy in ItsNat was the need of registering Groovy listeners in ItsNat Java listeners (view processors), when registering on an ItsNat (Java) listener the object registered is "frozen" and cannot be reloaded…​

This was the context to invent RelProxy and is the reason of the name, Reload + Proxy, the first feature of RelProxy was creating Java proxies (java.lang.reflect.Proxy) to wrap Groovy objects before registering into Java methods (ItsNat listeners), RelProxy proxies ever use the last version of the Groovy class by using the hot class loader of Groovy, by this way we can hot class reload classes in ItsNat…​ coded in Groovy.

The next step is obvious, Groovy class reloader is also a Groovy "compiler on demand", if we are able to make a "compiler on demand" for Java we can get THE SAME AS Groovy provides…​ in Java. As you know, as of Java 1.6 we have a Java API to access the compiler of your JDK.

If we are able to compile and load on demand Java source files, we can be more ambitious and be able to execute Java programs with no need of a previous compilation, just put into a file Java code like a UNIX shell script in a similar way Groovy does, this feature provides maybe the fastest "scripting" language of the world.

Why are you going to need Java as a scripting/shell language?

Because:

  1. Java is faaaast

  2. Java is robust: because there is compilation on the fly, syntax errors are detected with no need of executing the offending code.

  3. Java has an enormous amount of libraries

  4. class files can be optionally automatically saved to avoid compilation in the next execution

  5. Java 8 is around the corner providing a less verbose language

If you can execute uncompiled Java code, can we get something similar to an interactive shell to execute Java code snippets? Yes of course.

Do not confuse Java scripting provided by RelProxy with BeanShell, BeanShell is NOT Java, is a scripting language heavily inspired on Java < 1.5, running on the JVM with a nice integration with real Java, for instance in BeanShell there is no support of Java 1.5 features like generics.

Finally, RelProxy is in some way similar to the famous JRebel, but is not so transparent and has more limitations. The only advantage is the price, zero, and maybe lesser memory footprint and better performance.

That is all, welcome to dynamic Java, welcome to a more dynamic Groovy in a Java environment.

RelProxy: a fast alternative to context reloading for Java (and Groovy)

Most of Java application servers (servlet containers) have the context reloading feature. Context reloading is a great feature of Java servlet/EE application servers, it reloads the web application usually when a .class is changed in WEB-INF/classes. This feature is usually used in development with the help of your favourite IDE, when you change a Java source file the IDE automatically compiles the file and save the .class to WEB-INF/classes, this change triggers the context reloading loading the new changed class.

Context reloading is enough for small web applications in development phase but it becomes a productivity problem when the applications becomes big because any small change triggers the context reloading, the result is the context reloading being executed most of time making your system slow and producing soon a memory overflow (PermGen exception). Another caveat because the session is lost you must log again etc.

RelProxy-Java uses a different approach, it just recompile in memory the modified class but there is no class reload (which requires a new ClassLoader). When a exposed method of a registered singleton in JProxy (a utility of RelProxy for Java) is called, the class reloading happens. By this way you can make big changes without consuming much memory and CPU, and when changes are done, just with a simple page reload the registered singleton will be called producing the class reload.

RelProxy does not want to be a "full solution" for the problem of automatic class loading, with RelProxy you decide what code can be reloaded, this also reduce the impact of the tool because your are just going to reload just a subset of the source code of your web application (web applications are the target of the class reloading feature of RelProxy but other type of Java applications, for instance desktop, could be used.

RelProxy can be used in development only and/or in production, in the case of production, source code going to be reloaded is included into the war file (recommended of course under WEB-INF/), you can modify the Java source file directly in production and automatically changes are detected, recompiled in memory and reloaded when needed, no .class change is needed, by this way you get the similar experience when you change the source code in production of a PHP, Ruby, JSP etc files without the need of restarting the application. If you do not want use RelProxy in production just disable it, the performance penalty is zero.

RelProxy also can be used in development only accessing directly to your source files in /src folder (multiple source folders are allowed) with no need of uploading source code to production.

RelProxy is an alternative to context reloading, if your application is small and you feel comfortable with context reloading you do not need the class reloading features of RelProxy (RelProxy also offers Java scripting), otherwise you must disable context reloading when RelProxy is enabled.

Requisites and installation

RelProxy requires JDK 1.6 or upper, RelProxy have been tested on Oracle JDK 1.6, 1.7 and OpenJDK 1.7.

Just uncompress the RelProxy distribution file.

The distribution file has two important files:

  1. relproxy-X.Y.Z.jar : needed in classpath to use RelProxy in any form.

  2. jproxysh : needed whether command line scripting capabilities in Java are going to be used.

The distribution file includes some example scripts into the folder cmd_examples to test the shell capabilities or RelProxy Java, executing the script sh fixesforunix.sh is recommended to define the appropriated executable permissions, then define the environment variable JAVA_HOME with the location of your 1.6+ JDK installation.

Add RelProxy jar as Maven or Gradle dependency

As of v0.8.3 RelProxy is registered in Bintray/JCenter and Maven Central repositories. Add JCenter repository to your POM or Maven settings if you want to import from JCenter, in Gradle just add jcenter() to repositories.

Examples for version 0.8.8:

Maven POM:

      <dependency>
        <groupId>com.innowhere</groupId>
        <artifactId>relproxy</artifactId>
        <version>0.8.8</version>
      </dependency>

To add JCenter to your POM:

      <repositories>
        <repository>
          <id>jcenter</id>
          <url>http://jcenter.bintray.com</url>
        </repository>
      </repositories>

Gradle build:

dependencies {
  compile 'com.innowhere:relproxy:0.8.8'
}

To add JCenter:

repositories {
   jcenter()
}

Disabling automatic context reload

RelProxy is an alternative to context reloading, use of both has no sense and makes RelProxy useless, therefore we must disable context reloading.

In Eclipse and Tomcat

In this manual Eclipse 4.4 (Luna) has been used, behaviour of previous and future versions may be the same but not tested.

In Eclipse the Tomcat associated can have a configuration controlled only by Eclipse (the default mode), this configuration is only valid inside the Eclipse environment and the original configuration of Tomcat is untouched. By default Eclipse is configured to "Automatically publish when resources changed" for your concrete associated Tomcat, this option is required, to review this option go to menu Window / Show View / Servers, this menu option opens a view listing your servers, double click on the concrete Tomcat to show a configuration panel (if no server is associated to your Eclipse install a Tomcat back to Eclipse Servers view click the right button and select New / Server to associate the Tomcat to Eclipse).

In the configuration panel click on Publishing drop-down and review whether is correct.

Publishing

Now we are going to disable automatic context reload in a per web application/module basis (we can keep enabled in global configuration in Server Options).

Server Options

Click on Modules tab.

Web Modules

Disable the Auto Reload feature selecting the required module and clicking Edit…​

Edit Web Module

In NetBeans and Tomcat

In this manual NetBeans 8.0.2 has been used, behaviour of previous and future versions may be the same but not tested.

The author of this manual has not been able to disable context reloading feature of Tomcat in NetBeans environment. The xml archive with the <Context> descriptor in Tomcat installation is replaced with the content of META-INF/contex.xml, in theory just adding reloadable=true to <Context> in this file would make the job…​ no success, is ignored.

We are able of disabling context reload avoiding the automatic synchronization of sources and deployed artifacts, two flags are involved in Project Properties dialog:

Build / Compile / Compile on Save

Run / Deploy on Save

Just disabling Deploy on Save makes the job of avoiding .class changes and therefore context reloading.

This is valid for Maven web projects and Ant based projects generated by NetBeans’s wizards.

The price is the lost of automatic synchronization when single source files are changed in runtime. We will explain later how we can workaround this problem.

Configuring an Eclipse web application publishing Java and/or Groovy source code on the final target war

Dynamic Web Project

We are talking about a web application created by New / Dynamic Web Project (or New / Project…​ / Web / Dynamic Web Project) in Eclipse with Java source code going to be published in production usually in a folder under WEB-INF/ (for obvious privacy reasons).

Because this folder is also a source code folder, you must add it to the project configuration Properties / Java Build Path / Source / Add Folder.

This only affects to Java source folders, in case of using RelProxy-Groovy (GProxy) there is no need of configuring in Eclipse the folder with Groovy code (Groovy built-in compiler compiles Groovy files with no need of Eclipse).

Unfortunately Eclipse avoids publishing Java files and they are automatically filtered (not the case of .groovy files) in the web application internally deployed, there is no Eclipse configuration to avoid this filtering.

Installing and configuring the Eclipse Filesync Plugin resolves our problem.

Filesync Plugin installation

Configure Filesync in project Properties.

Filesync configuration

In this example the folder relproxy_ex_itsnat/WebContent/WEB-INF/javaex/code is a source code folder root containing .java files.

The tricky part is the Default target folder. This folder is the root of the real deployed web application, the final deployed web application is created under your workspace folder, you can get the path of this folder executing in your servlet init method:

    public void init(ServletConfig config) throws ServletException
    {
        super.init(config);

        ServletContext context = getServletContext();
        String realPath = context.getRealPath("/");
        String inputPath = realPath + "/WEB-INF/javaex/code/";

The variable inputPath contains the path to be configured as Default target folder in this example.

Sometimes the Filesync Plugin seems to fail, in this case you can need to force synchronization:

Force File Synchronization

An alternative is manual synchronization of files under /WEB-INF executing a custom Ant script like this:

<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="sync_production_src_folders" name="relproxy_ex_itsnat">

    <property file="conf/conf_relproxy.properties"/> <!-- defines ${webapp_target} where Eclipse builds the webapp -->

    <target name="sync_production_src_folders">

    	<echo message="Synchronizing (alternative to Filesync plugin) to: ${webapp_target}" />

        <property name="webinf_src" value="${basedir}/WebContent/WEB-INF" />
        <property name="webinf_target" value="${webapp_target}/WEB-INF" />

        <property name="source_java" value="${webinf_src}/javaex/code" />
        <property name="source_groovy" value="${webinf_src}/groovyex/code" />

        <property name="target_java" value="${webinf_target}/javaex/code" />
        <property name="target_groovy" value="${webinf_target}/groovyex/code" />

        <sync todir="${target_java}" includeEmptyDirs="true">
          <fileset dir="${source_java}"/>
        </sync>

        <sync todir="${target_groovy}" includeEmptyDirs="true">
          <fileset dir="${source_groovy}"/>
        </sync>

    </target>

</project>

After any modification of source code execute the synchronization task to be copied to the build directory created by Eclipse under workspace folder explained before.

Maven based Web Project

We can create a web project based on a Maven POM using the Maven archetype maven-archetype-webapp selecting New / Other / Maven / Maven Project, set in Filter the value maven-archetype-webapp.

In project Properties / Java Build Path / Libraries / Add Library add the server for instance Apache Tomcat v7.

The header of web.xml generated by Maven is a bit old (based on DOCTYPE), replace it with something like this (servlet 2.5, 3.0 is also valid for Tomcat v7):

web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
...
</web-app>

Follow the same steps described for an Eclipse Dynamic Web Project, yes in spite of most of things are defined in Maven we must repeat the same on Eclipse Project Properties dialogs, (for instance register the extra source code folders).

Because we are going to publish source code files (located in some place under WEB-INF/) to the final war we need to explain to Maven where are located these extra folders to be also included in compilation and copied to the final war, this configuration is done adding the extra source code folders as <directory> resources.

At the time of writing ItsNat is not in Maven Central repository, you must manually include it in your dependencies as a system dependency, and copy the jar to the WEB-INF/lib folder.

The following POM is a simple example of a ItsNat web application using RelProxy (v0.8.8) and including a published source code folder, src/main/webapp/WEB-INF/code, able to contain reloadable source code (remember to add ItsNat-1.3.1.jar to the WEB-INF/lib folder):

pom.xml
<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>com.innowhere</groupId>
    <artifactId>relproxy_ex_itsnat_maven</artifactId>
    <packaging>war</packaging>
    <version>0.1-SNAPSHOT</version>
    <name>relproxy_ex_itsnat_maven Maven Webapp</name>
    <url>https://github.com/jmarranz/relproxy/</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

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

      <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.1</version>
        <scope>provided</scope>
      </dependency>
      <!-- http://stackoverflow.com/tags/jstl/info http://stackoverflow.com/questions/2276083/include-jstl-dependency-with-maven -->

      <!--
      <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>jstl</artifactId>
          <version>1.2</version>
      </dependency>
      -->

      <dependency>
          <groupId>com.innowhere</groupId>
          <artifactId>relproxy</artifactId>
          <version>0.8.8</version>
      </dependency>

      <dependency>
          <groupId>ItsNat</groupId>
          <artifactId>ItsNat-jar</artifactId>
          <version>1.3.1</version>
          <scope>system</scope>
          <systemPath>${basedir}/src/main/webapp/WEB-INF/lib/ItsNat-1.3.1.jar</systemPath>
      </dependency>

      <dependency>
          <groupId>org.apache.xmlgraphics</groupId>
          <artifactId>batik-dom</artifactId>
          <version>1.7</version>
      </dependency>

      <dependency>
          <groupId>org.apache.xmlgraphics</groupId>
          <artifactId>batik-xml</artifactId>
          <version>1.7</version>
      </dependency>

      <dependency>
          <groupId>org.apache.xmlgraphics</groupId>
          <artifactId>batik-util</artifactId>
          <version>1.7</version>
      </dependency>

      <dependency>
          <groupId>net.sourceforge.nekohtml</groupId>
          <artifactId>nekohtml</artifactId>
          <version>1.9.12</version>
      </dependency>

      <dependency>
          <groupId>xalan</groupId>
          <artifactId>serializer</artifactId>
          <version>2.7.1</version>
      </dependency>

      <dependency>
          <groupId>org.codehaus.groovy</groupId>
          <artifactId>groovy-all</artifactId>
          <version>2.1.6</version>
      </dependency>

    </dependencies>

    <build>
        <finalName>relproxy_ex_itsnat_maven</finalName>

        <plugins>

            <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-compiler-plugin</artifactId>
              <version>2.0.2</version>
              <configuration>
                <source>1.6</source>
                <target>1.6</target>
                <encoding>${project.build.sourceEncoding}</encoding>
              </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.4.3</version>
                <configuration>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>

        </plugins>

        <resources>
           <resource>
             <directory>src/main/webapp/WEB-INF/groovyex/code</directory>
           </resource>
           <resource>
             <directory>src/main/webapp/WEB-INF/javaex/code</directory>
           </resource>
        </resources>
    </build>

</project>

In Eclipse there is no need of using org.codehaus.mojo:build-helper-maven-plugin plug-in (Maven support is "especial" in Eclipse), folders specified in <resources> are recognized as source folders.

If you have several folders with source code (RelProxy supports multiple hot reloadable root folders), add more <resource> elements, for instance:

    <resources>
       <resource>
         <directory>src/main/webapp/WEB-INF/groovyex/code</directory>
       </resource>
       <resource>
         <directory>src/main/webapp/WEB-INF/javaex/code</directory>
       </resource>
       <resource>
         <directory>src/main/webapp/WEB-INF/javaex/code2</directory>
       </resource>
    </resources>

Because we need to synchronize source code into /WEB-INF Filesync or this Ant file will help us:

sync.xml
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="sync_production_src_folders" name="relproxy_ex_itsnat_maven">

    <property file="conf/conf_relproxy.properties"/> <!-- defines ${webapp_target} where Eclipse builds the webapp -->

    <target name="sync_production_src_folders">

    	<echo message="Synchronizing (alternative to Filesync plugin) to: ${webapp_target}" />

        <property name="webinf_src" value="${basedir}/src/main/webapp/WEB-INF" />
        <property name="webinf_target" value="${webapp_target}/WEB-INF" />

        <property name="source_java" value="${webinf_src}/javaex/code" />
        <property name="source_groovy" value="${webinf_src}/groovyex/code" />

        <property name="target_java" value="${webinf_target}/javaex/code" />
        <property name="target_groovy" value="${webinf_target}/groovyex/code" />

        <sync todir="${target_java}" includeEmptyDirs="true">
          <fileset dir="${source_java}"/>
        </sync>

        <sync todir="${target_groovy}" includeEmptyDirs="true">
          <fileset dir="${source_groovy}"/>
        </sync>

    </target>

</project>

Configuring a NetBeans web application publishing Java and/or Groovy source code on the final target war

Remember you must avoid context reloading disabling first Run / Deploy on Save in Project Properties dialog.

Web Project based on Ant created by NetBeans

A conventional (not Maven) web project is created in NetBeans selecting the menu File / New Project / Java Web / Web Application.

By default he generated Ant file filters .java files under WEB-INF/ when deploying, to avoid this filtering just add to the build.xml:

build.xml
    <target name="-pre-dist">
        <copy todir="${build.web.dir}/WEB-INF" preservelastmodified="true">
            <fileset dir="${webinf.dir}" />
        </copy>
    </target>

Fortunately in this type of Ant based project, if the option Project Properties / Build / Compile / Compile on Save is enabled, NetBeans takes (by using Ant) care of automatic synchronization of resources of /web/WEB-INF and the same folder under /build, this includes .java files because we have avoided this filtering. There is no need of explicit synchronization by using Ant tasks.

Web Project based on Maven

A Maven web project is created in NetBeans selecting the menu File / New Project / Maven / Web Application.

Take a look again to the chapter about Eclipse Maven based web applications, the Maven POM structure is the same for NetBeans, remember you must specify extra source code folders under WEB-INF/ adding them as `<resource>`s.

Because the default structure of Maven, on development time when deploying a web application, Maven deploys under the target/projectname folder the final web application. Changed source files under src/webapp in runtime are not detected by RelProxy because the real files being used are really below target/projectname unless NetBeans automatically synchronizes files between both file trees. Effectively NetBeans automatically copies the modified file to the target/projectname, but unfortunately excluding .java (and .groovy) files.

In theory enabling Project Properties / Run / Deploy on Save does the job but enables automatic context reloading.

One simple solution is adding a special Ant task to synchronize the source files to the same files in target/projectname. Call this task after hot source code modification.

For instance:

sync.xml
<project basedir="." default="sync_production_src_folders" name="relproxy_ex_itsnat_maven_netbeans">

    <target name="sync_production_src_folders">

        <property name="webinf_src" value="${basedir}/src/main/webapp/WEB-INF" />

        <property name="webinf_target" value="${basedir}/target/relproxy_ex_itsnat_maven_netbeans-1.0-SNAPSHOT/WEB-INF" />

    	<echo message="Synchronizing (alternative to Filesync plugin) to: ${webinf_target}" />

        <property name="source_java" value="${webinf_src}/javaex/code" />
        <property name="source_groovy" value="${webinf_src}/groovyex/code" />

        <property name="target_java" value="${webinf_target}/javaex/code" />
        <property name="target_groovy" value="${webinf_target}/groovyex/code" />

        <sync todir="${target_java}" includeEmptyDirs="true">
          <fileset dir="${source_java}"/>
        </sync>

        <sync todir="${target_groovy}" includeEmptyDirs="true">
          <fileset dir="${source_groovy}"/>
        </sync>

    </target>

</project>

Configuring a NetBeans or Eclipse web application NOT publishing Java and/or Groovy source code on the final target war

As you have seen adding source code files under WEB-INF/ is very problematic, we have needed some tricks and workarounds to synchronize these source folders to the final deployed web application.

If you are not going to publish source code to production and you just need hot reload source code under your /src/main/java folder in Maven or src/java in NetBeans or /src in Eclipse, that is only in development phase, you do not need synchronization tricks because you tell RelProxy to directly access to the original source being modified for you and RelProxy recompiles and loads your changes in memory.

Both types of web applications, publishing source code to war and direct access to normal source code, are going to be shown you using concrete examples.

GProxy or how to be able to reload Groovy classes on the Java environment

com.innowhere.relproxy.gproxy.GProxy is the main Java class of RelProxy to provide this feature, with GProxy you can create Java proxies for Groovy objects because a java.lang.reflect.Proxy wrapper is passed instead of the original Groovy object, the original Groovy object is retained under the hood and method calls to the proxy are redirected to the real object calling the corresponding method using reflection. When the source code of the Groovy class changes GProxy automatically reloads the Groovy class and creates a new object to replace the old one, the fields of the original object are got and re-set to the new object to keep the state (number of fields and types must be the same otherwise reloading is not possible and a redeploy is required).

The following code is an example of how to use RelProxy along with ItsNat web framework, this code is included in the examples of RelProxy (relproxy_ex_itsnat or relproxy_ex_itsnat_maven).

The servlet variable is a servlet object containing a just configured groovy.util.GroovyScriptEngine, the setting up of this utility object is omitted:

groovy_servlet_init.groovy
package example.groovyex;

import org.itsnat.core.http.ItsNatHttpServlet;
import org.itsnat.core.tmpl.ItsNatDocumentTemplate;
import org.itsnat.core.event.ItsNatServletRequestListener;
import groovy.util.GroovyScriptEngine;
import java.lang.reflect.Method;
import com.innowhere.relproxy.RelProxyOnReloadListener;
import com.innowhere.relproxy.gproxy.GProxy;
import com.innowhere.relproxy.gproxy.GProxyGroovyScriptEngine;
import com.innowhere.relproxy.gproxy.GProxyConfig;


GroovyScriptEngine groovyEngine = servlet.getGroovyScriptEngine();

def gproxyGroovyEngine = {
             String scriptName -> return (java.lang.Class)groovyEngine.loadScriptByName(scriptName)
        } as GProxyGroovyScriptEngine;

def reloadListener = {
        Object objOld,Object objNew,Object proxy, Method method, Object[] args ->
           println("Reloaded " + objNew + " Calling method: " + method)
      } as RelProxyOnReloadListener;

def gpConfig = GProxy.createGProxyConfig();
gpConfig.setEnabled(true)
        .setRelProxyOnReloadListener(reloadListener)
        .setGProxyGroovyScriptEngine(gproxyGroovyEngine);

GProxy.init(gpConfig);


String pathPrefix = context.getRealPath("/") + "/WEB-INF/groovyex/pages/";

def docTemplate;
docTemplate = itsNatServlet.registerItsNatDocumentTemplate("groovyex","text/html", pathPrefix + "groovyex.html");

def db = new FalseDB();

ItsNatServletRequestListener listener = GProxy.create(new example.groovyex.GroovyExampleLoadListener(db), ItsNatServletRequestListener.class);
docTemplate.addItsNatServletRequestListener(listener);

Let’s explain the previous code:

def gproxyGroovyEngine = {
             String scriptName -> return (java.lang.Class)groovyEngine.loadScriptByName(scriptName)
        } as GProxyGroovyScriptEngine;

Defines a listener needed by GProxy to indirectly call the groovy.util.GroovyScriptEngine to load classes, take a look to the signature of GProxyGroovyScriptEngine there is no dependency with groovy.* packages, this is why you can use RelProxy in pure Java projects with no Groovy dependency in spite of Groovy support.

def reloadListener = {
        Object objOld,Object objNew,Object proxy, Method method, Object[] args ->
           println("Reloaded " + objNew + " Calling method: " + method)
      } as RelProxyOnReloadListener;

Defines an optional RelProxyOnReloadListener listener to be called when Groovy classes have been reloaded because some change has happened in the source code managed by RelProxy.

An object implementing this interface may be registered on RelProxy to listen when a method of the proxy object has been called (this example only includes one method exposed by the interface, but nothing prevents of adding more methods to the interface/implementation) and the class of the original object associated has been reloaded (a new "original" object based on the new class was created to replace it).

When you perform a source code change in source code managed by RelProxy the first time this method is called is the signal that changes has been detected and reloaded accordingly.

This interface and behavior is not GProxy specific and will be also used in JProxy for Java.

def gpConfig = GProxy.createGProxyConfig();
gpConfig.setEnabled(true)
        .setRelProxyOnReloadListener(reloadListener)
        .setGProxyGroovyScriptEngine(gproxyGroovyEngine);

GProxy.init(gpConfig);

Configures GProxy, now it is ready to proxy Groovy objects.

Take a look to the optional setEnabled(true) configuration call, GProxy is enabled by default, this means proxied Groovy objects are instrumented for hot reload. Calling setEnabled(false) tells GProxy to ignore any other configuration, GProxy is disabled and no proxy is created calling GProxy.create, the original Groovy objects will be returned with absolutely no performance penalty, this is the preferred configuration in production whether you do not want hot class reload in production.

The final code:

def db = new FalseDB();

ItsNatServletRequestListener listener = GProxy.create(new example.groovyex.GroovyExampleLoadListener(db), ItsNatServletRequestListener.class);
docTemplate.addItsNatServletRequestListener(listener);

is an example of proxying a example.groovyex.GroovyExampleLoadListener object and registering the returned Java proxy into the ItsNat infrastructure. The class example.groovyex.GroovyExampleLoadListener implements the ItsNat standard interface ItsNatServletRequestListener implementing the method processRequest(ItsNatServletRequest request, ItsNatServletResponse response) this method is called by ItsNat when a page rendered by the template is loaded, the proxy object receives this call and forwards this call to the latest class loaded, we are going to see more details later.

Let’s go to take a look to example.groovyex.GroovyExampleLoadListener:

GroovyExampleLoadListener.groovy
package example.groovyex;

import org.itsnat.core.event.ItsNatServletRequestListener;
import org.itsnat.core.ItsNatServletRequest;
import org.itsnat.core.ItsNatServletResponse;
import example.groovyex.FalseDB;

class GroovyExampleLoadListener implements ItsNatServletRequestListener
{
    def db

    GroovyExampleLoadListener()
    {
    }

    GroovyExampleLoadListener(FalseDB db) // Explicit type tells Groovy to reload FalseDB class when changed
    {
        this.db = db;
    }

    void processRequest(ItsNatServletRequest request, ItsNatServletResponse response)
    {
        println("GroovyExampleLoadListener 4 ");

        new example.groovyex.GroovyExampleDocument(request.getItsNatDocument(),db);
    }
}

To understand this code let’s to explain how ItsNat works, the method processRequest is called every time a page is loaded specifying the same ItsNat template, because this listener was registered as its load processor.

When RelProxy (through groovy.util.GroovyScriptEngine) detects the source code of the class GroovyExampleLoadListener or dependent classes like GroovyExampleDocument have changed, all classes with associated hot reloadable source, are reloaded and a new ClassLoader is created for them, next calls to GroovyExampleLoadListener proxy will use the new loaded class and the same with dependent classes.

However a concrete GroovyExampleLoadListener object was used to register, how can we reload a class with one live object already created?

The GroovyExampleLoadListener object was the one proxied, the class of this object is reloaded when a source change is detected (or any related class) because this is the objective of RelProxy, but this object can have fields pointing to objects usually loaded before registering/proxying the GroovyExampleLoadListener object. The classes of these attribute objects may be also reloaded but the new version is not effective because referenced objects are usually being used in other places, if we re-create these objects we are creating new instances for instance of objects designed to be singletons. This is the case of the db attribute of class FalseDB, this attribute references a concrete FalseDB object not able to be automatically reloaded in spite of the Groovy FalseDB class could be reloaded. This is why in case of the proxied object GroovyExampleLoadListener, RelProxy recreates the object based on the new loaded class by calling the default constructor and re-setting the attributes, by this way the new object is based on the new class containing the same attribute objects defined before, you cannot add, remove or change the type of attributes, if you do so RelProxy will not be able to hot reload and a new redeploy is needed.

The proxied class usually creates new objects based on dependent classes to execute some task, if no object of these dependent classes is "saved" and/or used outside of proxied environment RelProxy can reload dependent classes with no problem.

This is the case of the class GroovyExampleDocument and dependent classes (see the source code).

Other classes and interfaces like ItsNatServletRequest or ItsNatServletResponse are not reloaded in this example because they are ItsNat based and source code is not present in Groovy environment. FalseDB class could be reloaded but reloading will fail because the proxied object (GroovyExampleLoadListener) holds an attribute db of this class, RelProxy will say you the reloading process has been failed and a redeploy is recommended to effectively use the new version of the class.

In summary, in this ItsNat example, when source code of GroovyExampleLoadListener or dependent classes with source code controlled by RelProxy change, all of these classes are recompiled and reloaded by Groovy when changed. When the processRequest method of the proxied GroovyExampleLoadListener object is called because an end user is reloading the related web page, GProxy detects the singleton has been reloaded and recreates the GroovyExampleLoadListener object with the new Class re-setting the fields and finally the processRequest method is called and method processing is done using the new version of dependent classes.

Finally we have been able to reload Groovy classes mixed in a Java environment.

JProxy or how to be able to reload Java classes without using Java agents

Java hot reloadable proxies are very similar to Groovy support of RelProxy, in this case the task of detecting source changes, recompiling and reloading is fully done by RelProxy (in case of Groovy provided groovy.util.GroovyScriptEngine does most of this work).

com.innowhere.relproxy.jproxy.JProxy is the main Java class of RelProxy for hot reload of pure Java, with JProxy you can create Java java.lang.reflect.Proxy proxies wrapping your original objects to be passed to listeners, the original object is retained under the hood and method calls to the proxy are redirected to the real object calling the corresponding method using reflection. When the source code of a monitored Java file is changed, it is automatically recompiled in memory. When the processRequest method of the proxied JProxyExampleLoadListener object is called because an end user is reloading the related web page, JProxy detects something has changed and reload all monitored classes with a new ClassLoader, because the singleton class has been reloaded JProxy recreates the JProxyExampleLoadListener object with the new Class re-setting the fields to keep the state (number of fields and types must be the same otherwise reloading is not possible and a redeploy is required) and finally the processRequest method is called and method processing is done using the new version of dependent classes.

As you can see reloading only happens when hot reloadable classes are going to be used, only recompiling is done when some file is changed, this is a performance and memory improvement over the typical "context reloading per file save".

The following code is an example of how to use JProxy along with ItsNat web framework, this code is part of the RelProxy examples (relproxy_ex_itsnat or relproxy_ex_itsnat_maven) basically doing the same as the Groovy example:

JProxyExampleServlet.java
package example.javaex;

import java.io.File;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaFileObject;

import org.itsnat.core.event.ItsNatServletRequestListener;
import org.itsnat.core.http.HttpServletWrapper;
import org.itsnat.core.tmpl.ItsNatDocumentTemplate;
import com.innowhere.relproxy.RelProxyOnReloadListener;
import com.innowhere.relproxy.jproxy.JProxy;
import com.innowhere.relproxy.jproxy.JProxyCompilerListener;
import com.innowhere.relproxy.jproxy.JProxyConfig;
import com.innowhere.relproxy.jproxy.JProxyDiagnosticsListener;
import com.innowhere.relproxy.jproxy.JProxyInputSourceFileExcludedListener;


/**
 *
 * @author jmarranz
 */
public class JProxyExampleServlet extends HttpServletWrapper
{
    public JProxyExampleServlet()
    {
    }

    @Override
    public void init(ServletConfig config) throws ServletException
    {
        super.init(config);

        ServletContext context = getServletContext();
        String realPath = context.getRealPath("/");
        String inputPath = realPath + "/WEB-INF/javaex/code/";
        String classFolder = null; // Optional: context.getRealPath("/") + "/WEB-INF/classes";
        Iterable<String> compilationOptions = Arrays.asList(new String[]{"-source","1.6","-target","1.6"});
        long scanPeriod = 300;

        RelProxyOnReloadListener proxyListener = new RelProxyOnReloadListener() {
            @Override
            public void onReload(Object objOld, Object objNew, Object proxy, Method method, Object[] args) {
                System.out.println("Reloaded " + objNew + " Calling method: " + method);
            }
        };


        JProxyInputSourceFileExcludedListener excludedListener = new JProxyInputSourceFileExcludedListener()
        {
            @Override
            public boolean isExcluded(File file, File rootFolderOfSources)
            {
            	return false;
            }
        };

        JProxyCompilerListener compilerListener = new JProxyCompilerListener(){
            @Override
            public void beforeCompile(File file)
            {
                System.out.println("Before compile: " + file);
            }

            @Override
            public void afterCompile(File file)
            {
                System.out.println("After compile: " + file);
            }
        };

        JProxyDiagnosticsListener diagnosticsListener = new JProxyDiagnosticsListener()
        {
            @Override
            public void onDiagnostics(DiagnosticCollector<JavaFileObject> diagnostics)
            {
                List<Diagnostic<? extends JavaFileObject>> diagList = diagnostics.getDiagnostics();
                int i = 1;
                for (Diagnostic<? extends JavaFileObject> diagnostic : diagList)
                {
                   System.err.println("Diagnostic " + i);
                   System.err.println("  code: " + diagnostic.getCode());
                   System.err.println("  kind: " + diagnostic.getKind());
                   System.err.println("  line number: " + diagnostic.getLineNumber());
                   System.err.println("  column number: " + diagnostic.getColumnNumber());
                   System.err.println("  start position: " + diagnostic.getStartPosition());
                   System.err.println("  position: " + diagnostic.getPosition());
                   System.err.println("  end position: " + diagnostic.getEndPosition());
                   System.err.println("  source: " + diagnostic.getSource());
                   System.err.println("  message: " + diagnostic.getMessage(null));
                   i++;
                }
            }
        };

        JProxyConfig jpConfig = JProxy.createJProxyConfig();
        jpConfig.setEnabled(true)
                .setRelProxyOnReloadListener(proxyListener)
                .setInputPath(inputPath)
                .setJProxyInputSourceFileExcludedListener(excludedListener)
                .setScanPeriod(scanPeriod)
                .setClassFolder(classFolder)
                .setCompilationOptions(compilationOptions)
                .setJProxyCompilerListener(compilerListener)
                .setJProxyDiagnosticsListener(diagnosticsListener);

        JProxy.init(jpConfig);


        String pathPrefix = context.getRealPath("/") + "/WEB-INF/javaex/pages/";

        ItsNatDocumentTemplate docTemplate;
        docTemplate = itsNatServlet.registerItsNatDocumentTemplate("javaex","text/html", pathPrefix + "javaex.html");

        FalseDB db = new FalseDB();

        ItsNatServletRequestListener listener = JProxy.create(new example.javaex.JProxyExampleLoadListener(db), ItsNatServletRequestListener.class);
        docTemplate.addItsNatServletRequestListener(listener);
    }

}

There is more code than Groovy code because GroovyScriptEngine setting up was omitted (not specific of RelProxy) and now some configuration options are shown in spite of they may be optional.

Let’s explain the previous code:

    JProxyConfig jpConfig = JProxy.createJProxyConfig();
    jpConfig.setEnabled(true)
            .setRelProxyOnReloadListener(proxyListener)
            .setInputPath(inputPath)
            .setJProxyInputSourceFileExcludedListener(excludedListener)
            .setScanPeriod(scanPeriod)
            .setClassFolder(classFolder)
            .setCompilationOptions(compilationOptions)
            .setJProxyCompilerListener(compilerListener)
            .setJProxyDiagnosticsListener(diagnosticsListener);

    JProxy.init(jpConfig);

This is an example of JProxy configuration.

  • setEnabled(boolean) configuration method is the same as GProxy, when setting to false other configuration options are ignored, there is no hot reload and proxying and performance penalty is zero.

  • setRelProxyOnReloadListener(proxyListener) is the same as GProxy in fact the same interface RelProxyOnReloadListener is shared between GProxy and JProxy.

  • setInputPath(inputPath) defines where the source code files of hot reloadable classes is. The variant method setInputPaths(String[]) allows registering several root folders and setJProxyInputSourceFileExcludedListener(JProxyInputSourceFileExcludedListener) allows excluding concrete files.

  • setJProxyInputSourceFileExcludedListener(excludedListener) optionally defines whether the specified folder or file is excluded in the recompiling detection. In this example nothing is excluded.

  • setScanPeriod(scanPeriod) defines the period (in ms) between checks of timestamps of source code files to detect changes.

  • setClassFolder(classFolder) optionally defines where to save, as .class files, the bytecode resulting of re-compiling modified source files in runtime. By this way the next time the application is started .class files are aligned with source files and no runtime compilation is needed (class folder of course must be in classpath).

  • setCompilationOptions(compilationOptions) optionally sets the list of options you want for compiling phase, these are the same kind of options you would provide to the javac command, internally the Java compiler API receives this parameters and the reason of the required format.

  • setJProxyCompilerListener(compilerListener) optionally registers a listener to be called when a file is going to be compiled.

  • setJProxyDiagnosticsListener(diagnosticsListener) optionally registers the JProxyDiagnosticsListener listener to be executed when some warning or error happens compiling Java code, when providing null or not called RelProxy uses a default listener very similar to this example.

The final code:

    FalseDB db = new FalseDB();

    ItsNatServletRequestListener listener = JProxy.create(new example.javaex.JProxyExampleLoadListener(db), ItsNatServletRequestListener.class);
    docTemplate.addItsNatServletRequestListener(listener);

Is symmetric to Groovy counterpart, it is the same example and the same expected behavior of RelProxy but all in Java.

Anyway this is the code of JProxyExampleLoadListener:

JProxyExampleLoadListener.java
package example.javaex;

import org.itsnat.core.event.ItsNatServletRequestListener;
import org.itsnat.core.ItsNatServletRequest;
import org.itsnat.core.ItsNatServletResponse;
import org.itsnat.core.html.ItsNatHTMLDocument;

public class JProxyExampleLoadListener implements ItsNatServletRequestListener
{
    protected FalseDB db;

    public JProxyExampleLoadListener()
    {
    }

    public JProxyExampleLoadListener(FalseDB db)
    {
        this.db = db;
    }

    public void processRequest(ItsNatServletRequest request, ItsNatServletResponse response)
    {
        System.out.println("JProxyExampleLoadListener 4 " + this.getClass().getClassLoader().hashCode());

        new example.javaex.JProxyExampleDocument(request,(ItsNatHTMLDocument)request.getItsNatDocument(),db);
    }
}

As of version 0.8.6 RelProxy also supports hot reload of inner classes including anonymous inner classes. An example of ItsNat code:

JProxyExampleLoadListener.java
EventListener listener = new EventListener()
{
     @Override
     public void handleEvent(Event evt)
     {
         ...
     }
 };

Element buttonElem = doc.getElementById("buttonId");
((EventTarget)buttonElem).addEventListener("click", JProxy.create(listener, EventListener.class) ,false);

Obviously the container (enclosing) class of the inner class must be reloadable.

Use of JProxy with multiple servlets

In the previous JProxy example we have supposed one single servlet requiring class reloading and context reloading disabled.

If you have more servlets or you are a purist developer, you can use a ServletContextListener:

JProxyServletContextListener.java
...
public class JProxyServletContextListener implements ServletContextListener
{
    @Override
    public void contextInitialized(ServletContextEvent sce)
    {
        System.out.println("ServletContextListener contextInitialized");

        ServletContext context = sce.getServletContext();
        ...
        JProxy.init(jpConfig);
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce)
    {
        System.out.println("ServletContextListener contextDestroyed");
        JProxy.stop();
    }
}

Registered on your web.xml:

web.xml
<web-app ...

   <listener>
	<listener-class>
             example.javaex.JProxyServletContextListener
        </listener-class>
   </listener>
</web-app>

The stop() call is used to stop the timer checker of source code changes, useful to avoid memory leaks when the context is reloaded and avoid warnings when the servlet container is stopping.

Finally in your servlet classes only register your singletons:

    @Override
    public void init(ServletConfig config) throws ServletException
    {
        super.init(config);

        ServletContext context = config.getServletContext();

        String pathPrefix = context.getRealPath("/") + "/WEB-INF/javaex/pages/";

        ItsNatDocumentTemplate docTemplate;
        docTemplate = itsNatServlet.registerItsNatDocumentTemplate("javaex","text/html", pathPrefix + "javaex.html");

        FalseDB db = new FalseDB();

        ItsNatServletRequestListener listener = JProxy.create(new example.javaex.JProxyExampleLoadListener(db), ItsNatServletRequestListener.class);
        docTemplate.addItsNatServletRequestListener(listener);
    }

Starting and stopping class change detection and reloading in JProxy

You can reduce to zero the footprint of RelProxy in production setting setEnabled(boolean) to false, however if you are a brave guy or girl and you want to make also hot changes in production…​ JProxy.start() and JProxy.stop() methods are for you.

Remember we must to define the period between source files checking for changes calling setScanPeriod(scanPeriod), RelProxy defines behind the scene a java.util.Timer for this task, of course every time source code is checked for changes needs some time and CPU use, because there are synchronizations between source code checking and proxies use, a very small performance penalty happens when checking source code. This is why we can stop source code checking if we are not able to make source code changes anymore reducing performance penalty to minimum, and we can call start again to detect any source change.

You can call several times to JProxy.start() and JProxy.stop() methods, if nothing is going to be done nothing is done without errors (both methods return true when a state change was effective), and they are thread safe.

Call ever to stop() in your ServletContextListener in the contextDestroyed(ServletContextEvent) method.

Using JProxy only in development time with no need of publishing code (a GWT example)

In previous chapters we have added new source code folders below WEB-INF/ folder, this configuration is very useful in production to be able to hot change your Java code, of course in production time you can remove these folders avoiding publishing source code before packaging to war and with a simple call setEnabled(false) disable JProxy with zero performance penalty, this makes JProxy helpful in development only but as you can easily figure out, adding source code under WEB-INF/ folder is not a good idea if you are not going to use this code in production.

With JProxy is not necessary to put the source code going to be reloaded under WEB-INF/, you can modify Java source code and reload it located in conventional source code folders.

Because JProxy is going to directly access to original source code, the folder synchronizing problems are gone in this use case.

We are going to illustrate this capability with a GWT RPC example using Eclipse. Besides how to use JProxy in a GWT-RPC project, in this chapter we are going to learn how we can exclude source files from the hot class reload system of RelProxy/JProxy because we are going to need this feature.

This example is for development phase only, nothing prevents of appliying both strategies because JProxy allows multiple source folders to monitor by using JProxyConfig.setInputPaths(String[]).

In GWT, JProxy only can be used to reload Java code executed in server, this is why we are going to apply JProxy to a GWT-RPC project (that is a client-server web application).

Install Eclipse (Eclipse 4.4 Luna was used for this example), install the Google Plugin for Eclipse (version 4.4 was used), only install GWT dependencies if you want (there is no need of Android and Google App Engine parts).

Download RelProxy distribution file and copy the relproxy-x.y.z.jar to /war/WEB-INF/lib/.

Select in Eclipse the menu option New / Other…​ / Google/ Web Application Project to create a GWT-RPC sample project (Google App Engine is not needed).

There is no need of disabling context reloading, it seems is already disabled in the default configuration of GWT.

In this example we have created the project with name relproxy_ex_gwt and package com.innowhere.relproxyexgwt, this is the structure of the generated source code:

relproxy_ex_gwt    (root folder of project)
  src
    com
      innowhere
        relproxyexgwt
          client
            GreetingService.java
            GreetingServiceAsync.java
            Relproxy_ex_gwt.java
          server
            GreetingServiceImpl.java
          shared
            FieldVerifier.java
          Relproxy_ex_gwt.gwt.xml

We are only be able to reload classes executed in server, that is, classes below server/ folder. This why the class GreetingServiceImpl.java is our focus, this is the generated code:

GreetingServiceImpl.java
package com.innowhere.relproxyexgwt.server;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.innowhere.relproxyexgwt.client.GreetingService;
import com.innowhere.relproxyexgwt.shared.FieldVerifier;


/**
 * The server side implementation of the RPC service.
 */
@SuppressWarnings("serial")
public class GreetingServiceImpl extends RemoteServiceServlet implements GreetingService {

    public String greetServer(String input) throws IllegalArgumentException {
        // Verify that the input is valid.
        if (!FieldVerifier.isValidName(input)) {
            // If the input is not valid, throw an IllegalArgumentException back to
            // the client.
            throw new IllegalArgumentException("Name must be at least 4 characters long");
        }

        String serverInfo = getServletContext().getServerInfo();
        String userAgent = getThreadLocalRequest().getHeader("User-Agent");

        // Escape data from the client to avoid cross-site script vulnerabilities.
        input = escapeHtml(input);
        userAgent = escapeHtml(userAgent);

        return "Hello, " + input + "!<br><br>I am running " + serverInfo + ".<br><br>It looks like you are using:<br>" + userAgent;
    }

    /**
     * Escape an html string. Escaping data received from the client helps to
     * prevent cross-site script vulnerabilities.
     *
     * @param html the html string to escape
     * @return the escaped string
     */
    private String escapeHtml(String html) {
        if (html == null) {
            return null;
        }
        return html.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
    }
}

This class is a servlet created to receive RPC requests from client following the interface pattern of the interface GreetingService shared by client and server code. We are not going to try to reload this servlet because to use JProxy we need a reloadable singleton implementing an interface registered in JProxy, therefore we are deeply transforming GreetingServiceImpl:

GreetingServiceImpl.java
package com.innowhere.relproxyexgwt.server;

import java.io.File;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaFileObject;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.innowhere.relproxy.RelProxyOnReloadListener;
import com.innowhere.relproxy.jproxy.JProxy;
import com.innowhere.relproxy.jproxy.JProxyConfig;
import com.innowhere.relproxy.jproxy.JProxyDiagnosticsListener;
import com.innowhere.relproxy.jproxy.JProxyInputSourceFileExcludedListener;
import com.innowhere.relproxy.jproxy.JProxyCompilerListener;
import com.innowhere.relproxyexgwt.client.GreetingService;

/**
 * The server-side implementation of the RPC service.
 */
@SuppressWarnings("serial")
public class GreetingServiceImpl extends RemoteServiceServlet implements
		GreetingService {

    protected GreetingServiceDelegate delegate;

    public void init(ServletConfig config) throws ServletException {

        super.init(config);

        ServletContext context = config.getServletContext();

        String inputPath = context.getRealPath("/") + "/../src/";
        if (!new File(inputPath).exists())
        {
            System.out.println("RelProxy is disabled, detected production mode ");
            return;
        }

        JProxyInputSourceFileExcludedListener excludedListener = new JProxyInputSourceFileExcludedListener()
        {
            @Override
            public boolean isExcluded(File file, File rootFolder) {
                String absPath = file.getAbsolutePath();
                if (file.isDirectory())
                {
                    return absPath.endsWith(File.separatorChar + "client") ||
                           absPath.endsWith(File.separatorChar + "shared");
                }
                else
                {
                    return absPath.endsWith(GreetingServiceDelegate.class.getSimpleName() + ".java") ||
                           absPath.endsWith(GreetingServiceImpl.class.getSimpleName() + ".java");
                }
            }
        };

        String classFolder = null; // Optional: context.getRealPath("/") + "/WEB-INF/classes";
        Iterable<String> compilationOptions = Arrays.asList(new String[]{"-source","1.6","-target","1.6"});
        long scanPeriod = 200;

        RelProxyOnReloadListener proxyListener = new RelProxyOnReloadListener() {
            public void onReload(Object objOld, Object objNew, Object proxy, Method method, Object[] args) {
                System.out.println("Reloaded " + objNew + " Calling method: " + method);
            }
        };

        JProxyCompilerListener compilerListener = new JProxyCompilerListener(){
            @Override
            public void beforeCompile(File file)
            {
                System.out.println("Before compile: " + file);
            }

            @Override
            public void afterCompile(File file)
            {
                System.out.println("After compile: " + file);
            }
        };

        JProxyDiagnosticsListener diagnosticsListener = new JProxyDiagnosticsListener()
        {
            public void onDiagnostics(DiagnosticCollector<javax.tools.JavaFileObject> diagnostics)
            {
                List<Diagnostic<? extends JavaFileObject>> diagList = diagnostics.getDiagnostics();
                int i = 1;
                for (Diagnostic<? extends JavaFileObject> diagnostic : diagList)
                {
                   System.err.println("Diagnostic " + i);
                   System.err.println("  code: " + diagnostic.getCode());
                   System.err.println("  kind: " + diagnostic.getKind());
                   System.err.println("  line number: " + diagnostic.getLineNumber());
                   System.err.println("  column number: " + diagnostic.getColumnNumber());
                   System.err.println("  start position: " + diagnostic.getStartPosition());
                   System.err.println("  position: " + diagnostic.getPosition());
                   System.err.println("  end position: " + diagnostic.getEndPosition());
                   System.err.println("  source: " + diagnostic.getSource());
                   System.err.println("  message: " + diagnostic.getMessage(null));
                   i++;
                }
            }
        };

        JProxyConfig jpConfig = JProxy.createJProxyConfig();
        jpConfig.setEnabled(true)
                .setRelProxyOnReloadListener(proxyListener)
                .setInputPath(inputPath)
                .setJProxyInputSourceFileExcludedListener(excludedListener)
                .setScanPeriod(scanPeriod)
                .setClassFolder(classFolder)
                .setCompilationOptions(compilationOptions)
                .setJProxyCompilerListener(compilerListener)
                .setJProxyDiagnosticsListener(diagnosticsListener);

        JProxy.init(jpConfig);

        this.delegate = JProxy.create(new GreetingServiceDelegateImpl(this), GreetingServiceDelegate.class);

    }   // init

    public String greetServer(String input) throws IllegalArgumentException
    {
            try
            {
                    return delegate.greetServer(input);
            }
            catch(IllegalArgumentException ex)
            {
                    ex.printStackTrace();
                    throw ex;
            }
            catch(Exception ex)
            {
                    ex.printStackTrace();
                    throw new RuntimeException(ex);
            }
    }

    public HttpServletRequest getThreadLocalRequestPublic()
    {
            return getThreadLocalRequest();
    }
}

Let’s review this JProxy-ready class. GreetingServiceImpl is a singleton in practice because is a servlet, therefore this attribute:

protected GreetingServiceDelegate delegate;

which hold the reloadable singleton registered on:

this.delegate = JProxy.create(new GreetingServiceDelegateImpl(this), GreetingServiceDelegate.class);

As you can see we have created the Java file GreetingServiceDelegateImpl.java the class to hold the singleton going to be reloaded, implementing the interface GreetingServiceDelegate. JProxy returns a proxy object "implementing" GreetingServiceDelegate exposed to the non-reloadable world.

Take a look to this listener, in the previous example it was trivial, in this case is very important:

        JProxyInputSourceFileExcludedListener excludedListener = new JProxyInputSourceFileExcludedListener()
        {
            @Override
            public boolean isExcluded(File file, File rootFolder) {
                String absPath = file.getAbsolutePath();
                if (file.isDirectory())
                {
                    return absPath.endsWith(File.separatorChar + "client") ||
                           absPath.endsWith(File.separatorChar + "shared");
                }
                else
                {
                    return absPath.endsWith(GreetingServiceDelegate.class.getSimpleName() + ".java") ||
                           absPath.endsWith(GreetingServiceImpl.class.getSimpleName() + ".java");
                }
            }
        };

Registered on:

        .setJProxyInputSourceFileExcludedListener(excludedListener)

This listener filters the Java source files that must be ignored by RelProxy/JProxy even when modified.

Because JProxy creates a new ClassLoader and reloads on it all hot-reloadable classes when someone is modified, classes inside client/ and shared/ folders must not be reloadable because has no sense in GWT.

When a folder inside a declared source folder of reloadable classes specified in configuration is going to be inspected for changed classes, the method isExcluded is called to check whether the complete folder must be excluded, this is very useful for big projects with a lot of not reloadable files. In this case classes inside client/ or shared/ are excluded. If a folder is not excluded, child files and folders into this folder are asked for excluding calling isExcluded, the class GreetingServiceImpl cannot be reloaded because it is a servlet and cannot be registered in JProxy because is already in use by the JavaEE servlet system. Finally GreetingServiceDelegate.java cannot be reloaded because is the interface exposed to the non-reloadable world.

In summary only server/ classes can be reloaded excluding the servlet class GreetingServiceImpl.java and GreetingServiceDelegate.java.

In this example there is only specified a root source code folder, RelProxy allows several root source code folder JProxyConfig.setInputPahts(String[]) instead of JProxyConfig.setInputPaht(String), the parameter File rootFolder specify the root source code folder containing the folder or file to apply excluding rules, this parameter can help you to define very complex excluding scenarios with several root source code folders. The web application relproxy_test_itsnat used to test RelProxy contains a complex excluding scenario with three root source folders.

This is the code of GreetingServiceDelegate :

GreetingServiceDelegate.java
package com.innowhere.relproxyexgwt.server;

public interface GreetingServiceDelegate {

    public String greetServer(String input) throws IllegalArgumentException;

}

And the code of GreetingServiceDelegateImpl.java, basically a copy/paste of the original servlet code.

GreetingServiceDelegateImpl.java
package com.innowhere.relproxyexgwt.server;

import com.innowhere.relproxyexgwt.shared.FieldVerifier;

public class GreetingServiceDelegateImpl implements GreetingServiceDelegate
{
    protected GreetingServiceImpl parent;

    public GreetingServiceDelegateImpl() // needed by JProxy
    {
    }

    public GreetingServiceDelegateImpl(GreetingServiceImpl parent)
    {
        this.parent = parent;
    }

    public String greetServer(String input) throws IllegalArgumentException {

        // Verify that the input is valid.
        if (!FieldVerifier.isValidName(input)) {
                // If the input is not valid, throw an IllegalArgumentException back to
                // the client.
                throw new IllegalArgumentException("Name must be at least 4 characters long");
        }

        String serverInfo = parent.getServletContext().getServerInfo();
        String userAgent = parent.getThreadLocalRequestPublic().getHeader("User-Agent");

        // Escape data from the client to avoid cross-site script vulnerabilities.
        input = escapeHtml(input);
        userAgent = escapeHtml(userAgent);

        return "Hello, " + input + "!<br><br>I am running " + serverInfo
                        + ".<br><br>It looks like you are using:<br>" + userAgent;
    }

    /**
     * Escape an html string. Escaping data received from the client helps to
     * prevent cross-site script vulnerabilities.
     *
     * @param html the html string to escape
     * @return the escaped string
     */
    private String escapeHtml(String html) {
            if (html == null) {
                    return null;
            }
            return html.replaceAll("&", "&amp;").replaceAll("<", "&lt;")
                            .replaceAll(">", "&gt;");
    }
}

Run this example (Run As/Web Application GWT Super Dev Mode), open this URL http://127.0.0.1:8888/Relproxy_ex_gwt.html in your browser and a screen like this is shown:

Fig 1

Click on Send to Server:

Fig 2

Click on the Close button.

Now we are going to modify on the fly the Java code of GreetingServiceDelegateImpl, just change "Hello" by "Hello <b>BROTHER</b>" and save:

        return "Hello <b>BROTHER</b>, " + input + "!<br><br>I am running " + serverInfo
                        + ".<br><br>It looks like you are using:<br>" + userAgent;

Back to browser, click again on "Send to Server":

Fig 3

As you can see in this case no page reload has been necessary because the requisite is to call the proxied method to reload classes, this call was made by a AJAX/RPC call.

In this example we made a very simple method change, adding more methods is not a problem but most of the time you will need to add new fields related to new classes, because GreetingServiceDelegateImpl is a singleton we cannot add, remove or change names and types of the fields of this class, to overcome this severe limitation create new classes avoiding the singleton pattern and move the code to them. Code something like this:

GreetingServiceDelegateImpl
	public String greetServer(String input) throws IllegalArgumentException {
		return new GreetingServiceProcessor(this).greetServer(input);
	}

Declared fields of GreetingServiceProcessor can change with no problem because this class can be reloaded and is instantiated by any call to GreetingServiceDelegateImpl.greetServer() with fresh data.

Solving jar manifest configuration problems in JProxy

When developing a Liferay 6.2 example using RelProxy a weird problem was manifested, RelProxy (JProxy) internally makes a call like this:

    Enumeration<URL> res = classLoader.getResources("javax/portlet");

This call is expected to return a list of URL-JAR pointing to .class files with package javax.portlet, in Liferay 6.2 the jar containing these classes is portlet.jar. In spite of this jar is defined in the classpath of the ClassLoader, nothing is returned. To fix this problem we just need to decompress this jar and add Name: javax/portlet to the file META-INF/MANIFEST.MF and package again.

As you have realized, this solution is "dirty" and problematic with "jar downloaders" like any build tool downloading Maven artifacts. This is why JProxy provides a workaround allowing direct specification of the problematic jars (use absolute paths), when necessary RelProxy uses the "brute force" to locate the .class of the required package "manually" inspecting the provided jars. JProxy adds a new configuration method to specify these jars:

    JProxyConfig.setRequiredExtraJarPaths(String[] inputJarPaths)

This problem has been found in a jar in Liferay, but nothing prevents similar problematic jars in other tools. To see this problem fixed in Liferay take a look to the JProxyServletContextListener.java file of Liferay Example included in RelProxy Examples.

This problem is uncommon, if JProxy throws an error of a class not found and this class is in a jar known by the ClassPath used to load JProxy, suspect this problem and try to add explicitly the path of the problematic jar.

Identity of returned proxies

A proxy object implements the specified interfaces (in practice only one is important), you can call to any methods of the interface, this call is forwarded by RelProxy to the original or reloaded object. Because a proxy object is also an Object, the methods equals(Object) and hashCode() may be also called, these calls are forwarded to the wrapped object of the proxy.

When calling equals(Object) method RelProxy detects the special case of passing a proxy parameter, in this case RelProxy obtains the current associated object to the proxy parameter and passes this object to the equals method. By this way we are able to check the identity of two objects indirectly using two proxy objects. This feature is very useful in collections when registering a proxy instead of the original object, if another proxy object associated to the same original object is used for instance to remove from collection, the equals(Object) method is correctly called passing the original object.

Example:

    ItsNatServletRequestListener original = new example.javaex.JProxyExampleLoadListener(db);
    ItsNatServletRequestListener proxy = JProxy.create(original, ItsNatServletRequestListener.class);
    ItsNatServletRequestListener proxy2 = JProxy.create(original, ItsNatServletRequestListener.class);
    System.out.println("EQUALS TEST (true if not reloaded): " + (proxy.equals(proxy2)));

A new shell scripting language named Java

When we think on a shell scripting language we think on sh or csh, or maybe on the scripting language of Windows Console (based on the old MSDOS), or maybe you know your preferred conventional dynamic language usually can be executed like another shell language, for instance Groovy, Python, Ruby or JavaScript.

But when you think Java like a new shell scripting language sure you say "it’s impossible".

No, it is possible, RelProxy includes a tool named jproxysh to make possible executing pure Java code like another shell scripting language.

The principle is simple and is very similar to Groovy scripting, Groovy compiles on the fly Groovy code saving in memory the compiled bytecode, by this way developers think Groovy script is interpreted and is not, the same approach is applied to Java through RelProxy. In the case of RelProxy, bytecode can be optionally saved as .class files to avoid compiling on the fly every time the script is executed. When the JVM is able to load .class files instead of compiling, code execution maybe extremely faster than conventional scripting languages interpreted line by line from sources, this is why the affirmation of Java as the fastest scripting language of the world is accurate.

Because pure Java is used and the standard compiler API, nothing prevents using Java scripting in the less verbose Java 8 (v1.8).

Defining a Java based shell scripting file

Let’s see the first example (some background of UNIX shell is required):

example_java_shell
#!/usr/bin/env jproxysh

String msg = args[0] + args[1];
System.out.println(msg);

System.out.println("example_java_shell 1 ");

example.javashellex.JProxyShellExample.exec();

The best way to think this script is like the content of the standard method main of a class with some invented name in the default package (no package), in fact, this is how it is managed internally by RelProxy.

We could use /bin/jproxysh or /usr/local/bin/jproxysh but we are forced to install RelProxy in a concrete fixed place, by using /usr/bin/env the command jproxysh will be located using the current PATH.

Save this file in a folder root of the dependent classes. The dependent class in this example is JProxyShellExample.

The hierarchy is:

<root_folder>
  example_java_shell           (file)
  example                      (folder)
    javashellex                (folder)
      JProxyShellExample.java  (file)

 
Yes, you are right, mentally add the .java extension to example_java_shell and you get the typical file hierarchy of a JavaSE program, in fact JProxy is ready to execute a conventional JavaSE program with no explicit compilation, this will be shown later.

The first requisite is that jproxysh must be accessible by the environment variable PATH, anyway executing this script is not direct, it requires some previous configuration:

  • First of all the JAVA_HOME environment variable is required.

  • The CLASSPATH environment variable must locate the relproxy-X.X.jar file and other folders and jars required by your Java application, conventions are the same than a typical JavaSE program.

  • Optionally you may specify JAVA_OPTS to provide runtime options for the JVM.

There are other optional environment variables in this case RelProxy specific:

  • JPROXYSH_CACHE_CLASS_FOLDER : defines where to save the .class files resulting of compiling on the fly the scripting code, this folder is automatically added to the class path, so when the script is loaded the second time the .class files are used instead of source code according to the typical source-binary timestamp rules (if source code is more recent the class is ignored and replaced with a new file).

  • JPROXYSH_COMPILATION_OPTIONS : compilations passed to the JDK compiler, the format is the same as the command line javac.

The following is an example of shell code (into a script file) to execute the previous example_java_shell, this example is included in RelProxy distribution:

ex_java_shell_launcher.sh
#!/bin/sh

RELPROXY_JAR=relproxy-0.8.8.jar

PROJECT=`dirname $0`/..

# set PROJECT env as absolute path
TMP_PWD=`pwd`
cd $PROJECT
PROJECT=`pwd`
cd $TMP_PWD

if [ -z "$JAVA_HOME" ]; then
    echo Missing JAVA_HOME environment variable, exiting...
    exit 1
fi

export PATH=$PATH:$PROJECT/bin
export CLASSPATH=$PROJECT/lib/$RELPROXY_JAR
export JAVA_OPTS="-client -Xmx100m"
# Nothing really required in JAVA_OPTS, just to test

export JPROXYSH_CACHE_CLASS_FOLDER="$PROJECT/tmp/java_shell_test_classes"
export JPROXYSH_COMPILATION_OPTIONS="-source 1.6 -target 1.6"

$PROJECT/cmd_examples/code/example_java_shell "HELLO " "WORLD!"

Because example_java_shell is a jproxysh based script, nothing prevents of being executed directly using jproxysh:

jproxysh $PROJECT/cmd_examples/code/example_java_shell "HELLO " "WORLD!"

Defining a complete Java class

As you have seen in example_java_shell example, you can access to other Java "scripting" classes from the initial scripting file, this is really interesting when your scripts become too large and you need state (attributes) more methods and so on, that is, you need more classes.

If you need your scripting code more structured, you have the option of defining a conventional class in the scripting main file.

Take a look to this example also included in RelProxy distribution (slightly modified):

example_java_shell_complete_class
#!/usr/bin/env jproxysh

import example.javashellex.JProxyShellExample;

public class example_java_shell_complete_class
{
    public static void main(String[] args)
    {
        String msg = args[0] + args[1];
        System.out.println(msg);

        System.out.println("example_java_shell_complete_class 1 ");

        JProxyShellExample.exec();
    }
}

example_java_shell_complete_class is a conventional class, you can add methods, attributes and so on, the only limitation is the name of the class, it must be the same as the container file (in this case the container file has not the .java extension).

You can execute this script by the same ways we executed example_java_shell, directly or as a parameter of jproxysh.

Scripting conventional JavaSE source code

The differences between the example_java_shell_complete_class script and a conventional Java source file are just the extension (missing) and the hash bang to execute jproxysh.

We can remove the hashbang and add the .java extension to the main scripting file, in this scenario the source code is the same as a conventional JavaSE application.

Instead of compiling with javac and executing with java command, you just must execute it with jproxysh

jproxysh $PROJECT/cmd_examples/code/example_normal_class.java "HELLO " "WORLD!"

Executing a Java code snippet

We have seen how much overcomplex can be our scripting files, what if you just need to execute one, or two or three sentences…​ You don’t need to create a Java shell scripting file, you can write down your script as a parameter and execute. RelProxy through jproxy allows executing Java code snippets on the fly.

The following is a shell script included in RelProxy distribution which executes a simple code snippet (the param -c indicates you are going to execute inline code):

ex_java_shell_snippet_launcher.sh
#!/bin/sh

RELPROXY_JAR=relproxy-0.8.8.jar

PROJECT=`dirname $0`/..

# set PROJECT env as absolute path
TMP_PWD=`pwd`
cd $PROJECT
PROJECT=`pwd`
cd $TMP_PWD

if [ -z "$JAVA_HOME" ]; then
    echo Missing JAVA_HOME environment variable, exiting...
    exit 1
fi

export PATH=$PATH:$PROJECT/bin
export CLASSPATH=$PROJECT/lib/$RELPROXY_JAR
export JAVA_OPTS="-client -Xmx100m"
# Nothing really required in JAVA_OPTS, just to test

export JPROXYSH_COMPILATION_OPTIONS="-source 1.6 -target 1.6"

jproxysh -c 'System.out.print("This code snippet says: ");' \
            'System.out.println("Hello World!!");'

You can execute a single code block (into a string parameter) or several blocks in several lines separated with "\", every block can contain several Java sentences.

Alternatively you can execute a complete class with a standard main method, RelProxy detects when you are specifying a sentence block or a complete class, in this case because there is no file holding the code, the class name must be known by default and must be jproxyMainClass. For instance:

jproxysh -c 'public class _jproxyMainClass_ { '  \
            ' public static void main(String[] args) { ' \
            '    System.out.print("This code snippet says: ");' \
            '    System.out.println("Hello World!!");' \
            '  }' \
            '}'

The interactive Java shell

When you need something more interactive, just like the Groovy shell, RelProxy provides a simple interactive shell.

To launch the interactive shell define the required environment variables like the code snippet example and execute jproxysh with no parameters:

jproxysh

A message info is shown and a prompt is shown waiting for your commands and or code. Write help to know the shell options, if the text written is not recognized like a command it is interpreted as Java code and saved in a buffer to be executed when you want writing the 'exec' command.

The interactive Java shell accepts a block of sentences or a complete class with a standard main method and name jproxyMainClass.

How to use RelProxy shell scripting in Windows

RelProxy does not provide a jproxysh version for Windows because you can easily build a mini-Linux/Unix in your Windows box with MinGW/MSYS.

Install MinGW/MSYS, you must be able to locate the shell launcher msys.bat in a folder like C:\MinGW\msys\1.0\ (exact location may change according to your installation folder).

Execute msys.bat and you will get a simple Linux shell environment, in this environment you can execute your typical Linux commands like ls, ps, find etc and of course launch the previous script files documented in this manual and included in RelProxy distribution.

You can go to your required folder in MSYS with a cd command like this:

cd "C:\Program Files\MyProgram"

Or using a Unix format:

cd "/c/Program Files/MyProgram"

MSYS console is enough for most of purposes, if you can also install mintty using the MinGW GUI or command based installer, calling mintty& in MSYS opens an even more sophisticated Linux console. Mintty has some problem with some keyboard characters editing Java code in the RelProxy interactive console, back to basic MSYS console when necesssary.

If you need to execute Linux shell scripts (for instance RelProxy based) from Windows without a Linux like interactive console, do something like this in your Windows script or console:

set PATH=C:\MinGW\msys\1.0\bin;%PATH%
sh <path to the shell file>

Where <path to the shell file> can have Windows or Linux format (e.g. /c/development/relproxy/cmd_examples/ex_java_shell_launcher.sh).

Java Scripting API implementation

RelProxy implements the official JSR-223 Java Scripting API as found in Java 1.6.

The following Java code shows how to create the Java Scripting factory, get an engine instance and execute some code:

// ...
JProxyConfig jpConfig = JProxy.createJProxyConfig();
jpConfig.setEnabled(true)
        .setRelProxyOnReloadListener(proxyListener)
        .setInputPath(inputPath)
        .setScanPeriod(scanPeriod)
        .setClassFolder(classFolder)
        .setCompilationOptions(compilationOptions)
        .setJProxyDiagnosticsListener(diagnosticsListener);

JProxyScriptEngineFactory factory = JProxyScriptEngineFactory.create();

ScriptEngineManager manager = new ScriptEngineManager();
manager.registerEngineName("Java", factory);

manager.getBindings().put("msg","HELLO GLOBAL WORLD!");

ScriptEngine engine = (JProxyScriptEngine)manager.getEngineByName("Java");

((JProxyScriptEngine)engine).init(jpConfig);

Bindings bindings = engine.createBindings();
bindings.put("msg","HELLO ENGINE SCOPE WORLD!");


StringBuilder code = new StringBuilder();
code.append( " javax.script.Bindings bindings = context.getBindings(javax.script.ScriptContext.ENGINE_SCOPE); \n");
code.append( " String msg = (String)bindings.get(\"msg\"); \n");
code.append( " System.out.println(msg); \n");
code.append( " bindings = context.getBindings(javax.script.ScriptContext.GLOBAL_SCOPE); \n");
code.append( " msg = (String)bindings.get(\"msg\"); \n");
code.append( " System.out.println(msg); \n");
code.append( " example.javashellex.JProxyShellExample.exec(engine); \n");
code.append( " return \"SUCCESS\";");

String result = (String)engine.eval( code.toString() , bindings);
System.out.println("RETURNED: " + result);

((JProxyScriptEngine)engine).stop(); // Necessary if scanPeriod > 0 was defined

As you can see initialization code is the same as in JProxy examples, returned ScriptEngine implements JProxyScriptEngine, this interface defines the same methods you are going to find in JProxy, the main difference between JProxy and JProxyScriptEngine (implementing ScriptEngine) is that JProxy use is based on static methods and JProxyScriptEngine in practice is a singleton. In theory you get a new ScriptEngine instance every time you call manager.getEngineByName("Java"), just call once, use the returned object as a singleton and you will get a similar environment to JProxy plus the capability of executing code snipets, otherwise concurrent conflicting can happen when competing several ScriptEngine objects (unless configured folders are different).

Inside the eval method, compilation phase is thread safe but not code execution, you can use several threads to call eval and execute concurrent lengthy tasks without execution blocking.

The last line:

((JProxyScriptEngine)engine).stop(); // Necessary if scanPeriod > 0 was defined

The interface JProxyScriptEngine defines the same methods you are going to find in JProxy, for instance the stop() method, this method is necessary whether you define a scanPeriod and you want to dispose the ScriptEngine (otherwise the ScriptEngine is looking for source changes forever), you can also register reloadable singletons calling JProxyScriptEngine.create(…​) like in JProxy`.

The scripting code can be the content of a main method with this signature:

public static Object main(javax.script.ScriptEngine engine,javax.script.ScriptContext context)

Or optionally you can define a complete Java class containing the previous main method and name jproxyMainClass, for instance:

public class _jproxyMainClass_ {
  public static Object main(javax.script.ScriptEngine engine,javax.script.ScriptContext context) {
    javax.script.Bindings bindings = context.getBindings(javax.script.ScriptContext.ENGINE_SCOPE);
    // ...
  }
}

You can directly call the JProxyScriptEngineFactory.getScriptEngine() method without registering on a ScriptEngineManager, in this case avoid calling ServiceContext.getBindings(javax.script.ScriptContext.GLOBAL_SCOPE) because the default global scope Bindings object is not defined. In fact the method ScriptEngineManager.getEngineByName(String) calls JProxyScriptEngineFactory.getScriptEngine() and may return null if the JProxyScriptEngineFactory.getScriptEngine() method throws an exception for instance when some configuration data is wrong, because there is no log info of this exception you have no way to know what is happening, in this case directly call JProxyScriptEngineFactory.getScriptEngine() to know what is happening.

Example:

// ...

JProxyScriptEngineFactory factory = JProxyScriptEngineFactory.create();

ScriptEngine engine = factory.getScriptEngine();

((JProxyScriptEngine)engine).init(jpConfig);

Bindings bindings = engine.createBindings();
bindings.put("msg","HELLO ENGINE SCOPE WORLD!");

StringBuilder code = new StringBuilder();
code.append( " javax.script.Bindings bindings = context.getBindings(javax.script.ScriptContext.ENGINE_SCOPE); \n");
code.append( " String msg = (String)bindings.get(\"msg\"); \n");
code.append( " System.out.println(msg); \n");
code.append( " example.javashellex.JProxyShellExample.exec(engine); \n");
code.append( " return \"SUCCESS\";");

String result = (String)engine.eval( code.toString() , bindings);
System.out.println("RETURNED: " + result);

((JProxyScriptEngine)engine).stop(); // Necessary if scanPeriod > 0 was defined

If you need two or more different configurations, create two or more JProxyScriptEngine with different configurations.

Embedding RelProxy Java in your Java framework to provide hot reload

RelProxy use requires some RelProxy explicit code to provide hot class reloading. If you are a developer of a Java framework, or an autonomous Java service module in general, you can built-in RelProxy Java in your framework to transparently provide code autoreload to end user code using your framework without explicit use of the RelProxy API in your end user code beyond some required configuration.

There are two kind of APIs for using the Java part of RelProxy:

  1. JProxy class and related: mainly static methods

  2. Java Scripting API: based on interfaces

The second option is preferred to embed RelProxy Java in your Java framework because it is based on interfaces, no public class of RelProxy is needed to expose in your API, because bootstrap can be executed into your framework. We are going to use the simplified version of the API using JProxyScriptEngineFactory.create().

The JProxyScriptEngine has been designed to provided the same functionality than JProxy, that is, the same methods, but in this case using a pure interface.

A simple example is the best way to show how to embed RelProxy, this example, RelProxyBuiltin (project relproxy_builtin_ex), is included in the RelProxy Examples repository. It defines two listeners to be implemented and registered by end user code, one listener is to show the options and the other to execute the corresponding selected action.

This mini framework and use example is developed using NetBeans and Maven.

There are two packages:

  1. com.innowhere.relproxy_builtin_ex : the mini framework. The subpackage com.innowhere.relproxy_builtin_ex.impl contains the only non-public class of the framework.

  2. com.innowhere.relproxy_builtin_ex_main : a simple use example.

The mini framework (public class and interfaces):

RelProxyBuiltinRoot.java
package com.innowhere.relproxy_builtin_ex;

import com.innowhere.relproxy_builtin_ex.impl.RelProxyBuiltinImpl;

public class RelProxyBuiltinRoot
{
    private final static RelProxyBuiltinImpl SINGLETON = new RelProxyBuiltinImpl();

    public static RelProxyBuiltin get()
    {
        return SINGLETON;
    }
}
RelProxyBuiltin.java
package com.innowhere.relproxy_builtin_ex;

import com.innowhere.relproxy.jproxy.JProxyScriptEngine;
import java.io.InputStream;
import java.io.PrintStream;

public interface RelProxyBuiltin
{
    public JProxyScriptEngine getJProxyScriptEngine();

    public void addOutputListener(OutputListener listener);
    public void removeOutputListener(OutputListener listener);
    public int getOutputListenerCount();

    public void addCommandListener(CommandListener listener);
    public void removeCommandListener(CommandListener listener);
    public int getCommandListenerCount();

    public void runLoop(InputStream in,PrintStream out);
}
OutputListener.java
package com.innowhere.relproxy_builtin_ex;

import java.io.PrintStream;

public interface OutputListener
{
    public void write(PrintStream out);
}
CommandListener.java
package com.innowhere.relproxy_builtin_ex;

import java.io.PrintStream;

public interface CommandListener
{
    public void execute(String command,String input,PrintStream out);
}

Now the implementation details, this class shows how simple is to built-in RelProxy:

RelProxyBuiltinImpl.java
package com.innowhere.relproxy_builtin_ex.impl;

import com.innowhere.relproxy.jproxy.JProxyScriptEngine;
import com.innowhere.relproxy.jproxy.JProxyScriptEngineFactory;
import com.innowhere.relproxy_builtin_ex.CommandListener;
import com.innowhere.relproxy_builtin_ex.OutputListener;
import com.innowhere.relproxy_builtin_ex.RelProxyBuiltin;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.LinkedHashSet;
import java.util.Scanner;

public class RelProxyBuiltinImpl implements RelProxyBuiltin
{
    protected JProxyScriptEngine jProxyEngine = null;
    protected LinkedHashSet<OutputListener> outListeners = new LinkedHashSet<OutputListener>();
    protected LinkedHashSet<CommandListener>  commandListeners = new LinkedHashSet<CommandListener>();

    @Override
    public JProxyScriptEngine getJProxyScriptEngine()
    {
        if (jProxyEngine == null) jProxyEngine = (JProxyScriptEngine)JProxyScriptEngineFactory.create().getScriptEngine();
        return jProxyEngine;
    }

    public JProxyScriptEngine getJProxyScriptEngineIfConfigured()
    {
        if (jProxyEngine == null || !jProxyEngine.isEnabled())
            return null;
        return jProxyEngine;
    }

    @Override
    public void addOutputListener(OutputListener listener)
    {
        JProxyScriptEngine jProxy = getJProxyScriptEngineIfConfigured();
        if (jProxy != null)
        {
            listener = jProxy.create(listener,OutputListener.class);
        }
        outListeners.add(listener);
    }

    @Override
    public void removeOutputListener(OutputListener listener)
    {
        JProxyScriptEngine jProxy = getJProxyScriptEngineIfConfigured();
        if (jProxy != null)
        {
            listener = jProxy.create(listener,OutputListener.class);
        }
        outListeners.remove(listener);
    }

    @Override
    public int getOutputListenerCount()
    {
        return outListeners.size();
    }

    @Override
    public void addCommandListener(CommandListener listener)
    {
        JProxyScriptEngine jProxy = getJProxyScriptEngineIfConfigured();
        if (jProxy != null)
        {
            listener = jProxy.create(listener,CommandListener.class);
        }
        commandListeners.add(listener);
    }

    @Override
    public void removeCommandListener(CommandListener listener)
    {
        JProxyScriptEngine jProxy = getJProxyScriptEngineIfConfigured();
        if (jProxy != null)
        {
            listener = jProxy.create(listener,CommandListener.class);
        }
        commandListeners.remove(listener);
    }

    @Override
    public int getCommandListenerCount()
    {
        return commandListeners.size();
    }

    @Override
    public void runLoop(InputStream in,PrintStream out)
    {
        Scanner scanner = new Scanner(in);

        while(true)
        {
            out.print("Enter phrase:");

            String input = scanner.nextLine();

            out.println("Command list:");

            for(OutputListener listener : outListeners)
                listener.write(out);

            out.print("Enter command (or quit):");
            String command = scanner.nextLine();
            if ("quit".equals(command))
                break;

            for(CommandListener listener : commandListeners)
                listener.execute(command,input,out);
        }
    }
}

These three methods are enough to explain how to bootstrap RelProxy Java engine and how easy is to instrument listener registering hot reloadable:

RelProxyBuiltinImpl.java (partial)
    @Override
    public JProxyScriptEngine getJProxyScriptEngine()
    {
        if (jProxyEngine == null) jProxyEngine = (JProxyScriptEngine)JProxyScriptEngineFactory.create().getScriptEngine();
        return jProxyEngine;
    }

    public JProxyScriptEngine getJProxyScriptEngineIfConfigured()
    {
        if (jProxyEngine == null || !jProxyEngine.isEnabled())
            return null;
        return jProxyEngine;
    }

    @Override
    public void addOutputListener(OutputListener listener)
    {
        JProxyScriptEngine jProxy = getJProxyScriptEngineIfConfigured();
        if (jProxy != null)
        {
            listener = jProxy.create(listener,OutputListener.class);
        }
        outListeners.add(listener);
    }

The public method RelProxyBuiltin.getJProxyScriptEngine() must be called in starting time to configure RelProxy. If RelProxy is not configured and enabled there is no performance penalty.

Remember that the proxy object returned by create(…​) method correctly calls the method hashCode() and equals(Object) in the internal true listener object for identity purposes required by the listener collection/registry.

This is the example code of this console based program (names are inspired on JUnit but is not really a JUnit test example):

Main.java
package com.innowhere.relproxy_builtin_ex_main;

import com.innowhere.relproxy.RelProxyOnReloadListener;
import com.innowhere.relproxy.jproxy.JProxy;
import com.innowhere.relproxy.jproxy.JProxyCompilerListener;
import com.innowhere.relproxy.jproxy.JProxyConfig;
import com.innowhere.relproxy.jproxy.JProxyDiagnosticsListener;
import com.innowhere.relproxy.jproxy.JProxyInputSourceFileExcludedListener;
import com.innowhere.relproxy.jproxy.JProxyScriptEngine;
import com.innowhere.relproxy_builtin_ex.CommandListener;
import com.innowhere.relproxy_builtin_ex.RelProxyBuiltin;
import com.innowhere.relproxy_builtin_ex.RelProxyBuiltinRoot;
import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaFileObject;

public class Main
{
    public static void main(String[] args) throws Exception
    {
        new Main();
    }

    public Main()
    {
        // Note: NetBeans Console window works bad (no input) with Maven Test tasks http://stackoverflow.com/questions/3035351/broken-console-in-maven-project-using-netbeans
        // this is why is not a really JUnit test.
        setUp();
        try
        {
            mainTest();
        }
        finally
        {
            tearDown();
        }
        System.exit(0);
    }

    public void setUp()
    {
        URL res = this.getClass().getResource("/"); // .../target/classes/

        // Use example of RelProxy in development time:

        String inputPath = res.getFile() + "/../../src/main/java/";

        if (new File(inputPath).exists())
        {
            System.out.println("RelProxy to be enabled, development mode detected");
        }
        else
        {
            System.out.println("RelProxy disabled, production mode detected");
            return;
        }


        JProxyInputSourceFileExcludedListener excludedListener = new JProxyInputSourceFileExcludedListener()
        {
            @Override
            public boolean isExcluded(File file, File rootFolderOfSources)
            {
                String absPath = file.getAbsolutePath();

                if (file.isDirectory())
                {
                    return absPath.endsWith(File.separatorChar + "relproxy_builtin_ex");
                }
                else
                {
                    return absPath.endsWith(File.separatorChar + Main.class.getSimpleName() + ".java");
                }
            }
        };

        String classFolder = null; // Optional
        Iterable<String> compilationOptions = Arrays.asList(new String[]{"-source","1.6","-target","1.6"});
        long scanPeriod = 1000;

        RelProxyOnReloadListener proxyListener = new RelProxyOnReloadListener() {
            @Override
            public void onReload(Object objOld, Object objNew, Object proxy, Method method, Object[] args) {
                System.out.println("Reloaded " + objNew + " Calling method: " + method);
            }
        };

        JProxyCompilerListener compilerListener = new JProxyCompilerListener(){
            @Override
            public void beforeCompile(File file)
            {
                System.out.println("Before compile: " + file);
            }

            @Override
            public void afterCompile(File file)
            {
                System.out.println("After compile: " + file);
            }
        };

        JProxyDiagnosticsListener diagnosticsListener = new JProxyDiagnosticsListener()
        {
            @Override
            public void onDiagnostics(DiagnosticCollector<JavaFileObject> diagnostics)
            {
                List<Diagnostic<? extends JavaFileObject>> diagList = diagnostics.getDiagnostics();
                int i = 1;
                for (Diagnostic diagnostic : diagList)
                {
                   System.err.println("Diagnostic " + i);
                   System.err.println("  code: " + diagnostic.getCode());
                   System.err.println("  kind: " + diagnostic.getKind());
                   System.err.println("  line number: " + diagnostic.getLineNumber());
                   System.err.println("  column number: " + diagnostic.getColumnNumber());
                   System.err.println("  start position: " + diagnostic.getStartPosition());
                   System.err.println("  position: " + diagnostic.getPosition());
                   System.err.println("  end position: " + diagnostic.getEndPosition());
                   System.err.println("  source: " + diagnostic.getSource());
                   System.err.println("  message: " + diagnostic.getMessage(null));
                   i++;
                }
            }
        };

        RelProxyBuiltin rpbRoot = RelProxyBuiltinRoot.get();
        JProxyScriptEngine engine = rpbRoot.getJProxyScriptEngine();

        JProxyConfig jpConfig = JProxy.createJProxyConfig();
        jpConfig.setEnabled(true)
                .setRelProxyOnReloadListener(proxyListener)
                .setInputPath(inputPath)
                .setJProxyInputSourceFileExcludedListener(excludedListener)
                .setScanPeriod(scanPeriod)
                .setClassFolder(classFolder)
                .setCompilationOptions(compilationOptions)
                .setJProxyCompilerListener(compilerListener)
                .setJProxyDiagnosticsListener(diagnosticsListener);

        engine.init(jpConfig);

        System.out.println("RelProxy running");
    }

    public void tearDown()
    {
        RelProxyBuiltin rpbRoot = RelProxyBuiltinRoot.get();
        JProxyScriptEngine engine = rpbRoot.getJProxyScriptEngine();
        engine.stop();

        System.out.println("RelProxy stopped");
    }

    public void mainTest()
    {
        RelProxyBuiltin rpbRoot = RelProxyBuiltinRoot.get();

        TestListener listener = new TestListener();

        rpbRoot.addOutputListener(listener);
        assertTrue(rpbRoot.getOutputListenerCount() == 1);
        rpbRoot.removeOutputListener(listener);
        assertTrue(rpbRoot.getOutputListenerCount() == 0);

        rpbRoot.addOutputListener(listener);


        CommandListener commandListener = listener.getCommandListener();

        rpbRoot.addCommandListener(commandListener);
        assertTrue(rpbRoot.getCommandListenerCount() == 1);
        rpbRoot.removeCommandListener(commandListener);
        assertTrue(rpbRoot.getCommandListenerCount() == 0);

        rpbRoot.addCommandListener(commandListener);

        rpbRoot.runLoop(System.in,System.out);
    }


    private static void assertTrue(boolean res)
    {
        if (!res) throw new RuntimeException("Unexpected Error");
    }
}

Take a look to this piece of code:

Main.java (partial)
        URL res = this.getClass().getResource("/"); // .../target/classes/

        // Use example of RelProxy in development time:

        String inputPath = res.getFile() + "/../../src/main/java/";

        if (new File(inputPath).exists())
        {
            System.out.println("RelProxy to be enabled, development mode detected");
        }
        else
        {
            System.out.println("RelProxy disabled, production mode detected");
            return;
        }

        JProxyInputSourceFileExcludedListener excludedListener = new JProxyInputSourceFileExcludedListener()
        {
            @Override
            public boolean isExcluded(File file, File rootFolderOfSources)
            {
                String absPath = file.getAbsolutePath();

                if (file.isDirectory())
                {
                    return absPath.endsWith(File.separatorChar + "relproxy_builtin_ex");
                }
                else
                {
                    return absPath.endsWith(File.separatorChar + Main.class.getSimpleName() + ".java");
                }
            }
        };

We get and later register the root folder of the source code of our application, this code "may be" reloadable.

We need to exclude the source code of the framework because is not obviously end user code (not reloadable), and the Main.java file containing the test code, this class is not reloadable, only the class TestListener.java (in the same folder than Main.java) will be (and must be) reloadable. We could simplify this isExcluded method just excluding anything different to the file TestListener.java, but excluding whole folders is recommended to speed the traversing of the source code file tree because otherwise all not reloadable files, into not reloadable folders, are checked.

Finally the class TestListener.java containing both listeners, the CommandListener implemented is forced to be an anonymous inner class just for demonstrative purposes:

TestListener.java
package com.innowhere.relproxy_builtin_ex_main;

import com.innowhere.relproxy_builtin_ex.CommandListener;
import com.innowhere.relproxy_builtin_ex.OutputListener;
import java.io.PrintStream;

public class TestListener implements OutputListener
{
    @Override
    public void write(PrintStream out)
    {
        out.println("uppercase");
        out.println("lowercase");
    }

    public CommandListener getCommandListener()
    {
        return new CommandListener()
        {
            @Override
            public void execute(String command,String text,PrintStream out)
            {
                if ("uppercase".equals(command))
                    out.println(text.toUpperCase());
                else if ("lowercase".equals(command))
                    out.println(text.toLowerCase());
                else
                    out.println("Unknown command:" + command);
            }
        };
    }
}

Execute the Main class and try the predefined options. To check if RelProxy is working fine try to add a new option "same" without stopping the program:

    @Override
    public void write(PrintStream out)
    {
        out.println("uppercase");
        out.println("lowercase");

        out.println("same");  // NEW
    }

    public CommandListener getCommandListener()
    {
        return new CommandListener()
        {
            @Override
            public void execute(String command,String text,PrintStream out)
            {
                if ("uppercase".equals(command))
                    out.println(text.toUpperCase());
                else if ("lowercase".equals(command))
                    out.println(text.toLowerCase());

                else if ("same".equals(command)) // NEW
                    out.println(text); // NEW

                else
                    out.println("Unknown command:" + command);
            }
        };
    }
}

The next phrase to process includes now the "same" action with no need of stopping the console application.

Note: use RelProxy 0.8.7 or upper, this release adds an improvement when embedding by this way.