Commits

Ruwan Linton committed a5f3e31

Ability to reload jar files and binary classes referred by sequence snippets and classes, making sure the atomicity of the configuration switch.
This also provides the ability to have more than one version of the same class loaded in the JVM used by different configuration fragments

Comments (0)

Files changed (17)

modules/core/src/main/java/org/adroitlogic/ultraesb/ServerManager.java

 import org.adroitlogic.ultraesb.core.ConfigurationImpl;
 import org.adroitlogic.ultraesb.core.ProxyService;
 import org.adroitlogic.ultraesb.core.Sequence;
+import org.adroitlogic.ultraesb.core.compile.HotSwapClassLoader;
 import org.adroitlogic.ultraesb.core.config.AbstractConfigurationElement;
 import org.adroitlogic.ultraesb.core.endpoint.Endpoint;
 import org.adroitlogic.ultraesb.core.helper.LoggerUtilsManager;
 import org.adroitlogic.ultraesb.jmx.view.*;
 import org.adroitlogic.ultraesb.transport.TransportListener;
 import org.adroitlogic.ultraesb.transport.TransportSender;
+import org.apache.commons.io.FileUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
      * @param config the ConfigurationImpl instance
      */
     public void initialize(ApplicationContext rootContext, ConfigurationImpl config) {
+
+        // safety measure to make sure there is no disk storage leak due to hot swap class loader
+        // making a copy of custom lib path
+        File instanceJarRoot = new File(HotSwapClassLoader.INSTANCE_JAR_ROOT);
+        try {
+            if (instanceJarRoot.exists() && instanceJarRoot.isDirectory() && instanceJarRoot.list().length > 0) {
+                FileUtils.deleteDirectory(instanceJarRoot);
+            }
+            // make sure this precaution does not cause any unwanted issues in the startup
+        } catch (Exception ignore) {}
+
         this.rootContext = rootContext;
         if (rootContext instanceof ConfigurableWebApplicationContext) {
             String[] locations = ((ConfigurableWebApplicationContext) rootContext).getConfigLocations();
         defResponseEp.start();
 
         // start root context service elements
-        startDynamicElements(rootContext);
+        startDynamicElements(rootContext, Thread.currentThread().getContextClassLoader());
 
         logger.info("UltraESB server root configuration started successfully");
         state = ServerManager.State.STARTED;
      * to initialize any other beans
      * @param ctx the context to start elements from
      */
-    public void startDynamicElements(ApplicationContext ctx) {
+    public void startDynamicElements(ApplicationContext ctx, ClassLoader classLoader) {
 
         // start named endpoints
         Map<String, Endpoint> endpointMap = ctx.getBeansOfType(Endpoint.class);
         for (Sequence seq : sequenceMap.values()) {
             seq.setConfig(config);
             seq.setChildContext(ctx);
+            seq.setClassLoader(classLoader);
             seq.start();
             sequenceManagement.registerSequence(seq);
         }
             proxyManagement.registerProxyService(ps);
             ps.setConfig(config);
             ps.setChildContext(ctx);
+            ps.setClassLoader(classLoader);
 
             if (isLocalEndpoint(ps.getInDestination(), ps.getId(), ctx)) {
                 endpointManagement.registerEndpoint(ps.getInDestination());
                 ps.getOutDestination().setChildContext(ctx);
             }
 
+            // todo:ruwan: please review this, I do not think we need this code fragment here till ps.start line as
+            // todo: the ps.start method does all of this again..
             if (isLocalSequence(ps.getInSequence(), ps.getId(), ctx)) {
                 sequenceManagement.registerSequence(ps.getInSequence());
                 ps.getInSequence().setConfig(config);
      * Compiles and starts Sequences defined in a configuration
      * @param ctx child context to initialize
      */
-    public void prepareSequences(ApplicationContext ctx) {
+    public void prepareSequences(ApplicationContext ctx, ClassLoader classLoader) {
 
         // start named sequences
         Map<String, Sequence> sequenceMap = ctx.getBeansOfType(Sequence.class);
         for (Sequence seq : sequenceMap.values()) {
             seq.setConfig(config);
             seq.setChildContext(ctx);
+            seq.setClassLoader(classLoader);
             seq.setPrepared(true);
             seq.start();
         }
         for (ProxyService ps : proxyServices.values()) {
             ps.setConfig(config);
             ps.setChildContext(ctx);
+            ps.setClassLoader(classLoader);
             ps.startSequences(true);
         }
     }

modules/core/src/main/java/org/adroitlogic/ultraesb/core/ConfigurationImpl.java

 import org.adroitlogic.ultraesb.api.ConfigurationWatcher;
 import org.adroitlogic.ultraesb.api.FileCache;
 import org.adroitlogic.ultraesb.cache.CacheManager;
+import org.adroitlogic.ultraesb.core.compile.HotSwapClassLoader;
 import org.adroitlogic.ultraesb.core.config.AbstractConfigurationElement;
 import org.adroitlogic.ultraesb.core.config.JavaSequencePreparationException;
 import org.adroitlogic.ultraesb.core.endpoint.Address;
 import org.adroitlogic.ultraesb.core.work.WorkManager;
 import org.adroitlogic.ultraesb.transport.TransportListener;
 import org.adroitlogic.ultraesb.transport.TransportSender;
-import org.apache.commons.io.filefilter.DirectoryFileFilter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeansException;
 import org.springframework.core.io.FileSystemResource;
 
 import java.io.File;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 /**
  * The root class that holds the complete UltraESB configuration loaded from one or more configuration files
  *
+ * @since 1.0.0
  */
 public class ConfigurationImpl implements org.adroitlogic.ultraesb.api.Configuration,
     ApplicationContextAware, ApplicationListener {
 
     private boolean contextBeingDestroyed = false;
 
+    private Environment environment;
+
     /** Thread local to keep track of calling sequences */
     static ThreadLocal currentSubContext = new ThreadLocal();
 
      */
     public synchronized void addOrUpdateConfigFromFile(String filePath) {
 
-        GenericApplicationContext newContext  = prepareConfigFromFile(filePath);
+        ClassLoader classLoader = environment != null && environment.isBinaryClassReloadingEnabled() ?
+                new HotSwapClassLoader() : Thread.currentThread().getContextClassLoader();
+
+        GenericApplicationContext newContext  = prepareConfigFromFile(filePath, classLoader);
         GenericApplicationContext prevContext = childContexts.get(filePath);
 
         boolean encounteredFaults = false;
         try {
-            ServerManager.getInstance().startDynamicElements(newContext);
+            ServerManager.getInstance().startDynamicElements(newContext, classLoader);
             childContexts.put(filePath, newContext);
 
             // if an outdated config now remains, keep it within the outdated map
      * @param filePath the file path for the configuration fragment
      * @return the prepared GenericApplicationContext
      */
-    private GenericApplicationContext prepareConfigFromFile(String filePath) {
+    private GenericApplicationContext prepareConfigFromFile(String filePath, ClassLoader classLoader) {
 
         GenericApplicationContext ctx = new GenericApplicationContext(rootContext);
 
             }
 
             // Start sequences of the child
-            ServerManager.getInstance().prepareSequences(ctx);
+            // todo:ruwan: why do we need to do this, all of what this method does is also been done again
+            // todo: in the startDynamicElements call as well
+            ServerManager.getInstance().prepareSequences(ctx, classLoader);
             logger.info("Successfully prepared configuration : " + filePath);
             return ctx;
 
         this.dynamicSpringBeansEnabled = dynamicSpringBeansEnabled;
     }
 
+    public void setEnvironment(Environment environment) {
+        this.environment = environment;
+    }
+
     private void throwIllegalArgumentException(String msg) {
         logger.error(msg);
         throw new IllegalArgumentException(msg);

modules/core/src/main/java/org/adroitlogic/ultraesb/core/Environment.java

+/*
+ * AdroitLogic UltraESB Enterprise Service Bus
+ *
+ * Copyright (c) 2010-2012 AdroitLogic Private Ltd. (http://adroitlogic.org). All Rights Reserved.
+ *
+ * GNU Affero General Public License Usage
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
+ * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along with this program (See LICENSE-AGPL.TXT).
+ * If not, see http://www.gnu.org/licenses/agpl-3.0.html
+ *
+ * Commercial Usage
+ *
+ * Licensees holding valid UltraESB Commercial licenses may use this file in accordance with the UltraESB Commercial
+ * License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written
+ * agreement between you and AdroitLogic.
+ *
+ * If you are unsure which license is appropriate for your use, or have questions regarding the use of this file,
+ * please contact AdroitLogic at info@adroitlogic.com
+ */
+
+package org.adroitlogic.ultraesb.core;
+
+/**
+ * Defines the environment for UltraESB which controls the runtime behavior of the system. There are few pre-defined
+ * environments named as follows with the respective properties;</p>
+ *
+ * <table>
+ *     <thead>
+ *         <tr><th>Environment Name</th><th>Binary Class Reloading</th><th>Configuration Auto Refresh</th></tr>
+ *     </thead>
+ *     <tbody>
+ *         <tr><td>DEV</td><td>Enabled</td><td>Disabled</td></tr>
+ *         <tr><td>IDE</td><td>Enabled</td><td>Enabled</td></tr>
+ *         <tr><td>UNIT_TEST</td><td>Disabled</td><td>Disabled</td></tr>
+ *         <tr><td>SAMPLE</td><td>Disabled</td><td>Disabled</td></tr>
+ *         <tr><td>TEST</td><td>Enabled</td><td>Disabled</td></tr>
+ *         <tr><td>STAGE</td><td>Enabled</td><td>Disabled</td></tr>
+ *         <tr><td>PROD</td><td>Enabled</td><td>Disabled</td></tr>
+ *     </tbody>
+ * </table>
+ *
+ * <p>Any of the above environments can be specified as your environment, with specifying the name as a constructor
+ * argument to the {@link Environment}. The name is case insensitive so "DEV", "dev", "Dev" and "dEv" all represent
+ * the predefined dev environment. This environment doesn't require the properties as the system will use the
+ * properties as per the above table in defining the UltraESB system behavior.</p>
+ *
+ * <p>Custom user defined environments can also be used, which requires the specification of the properties associated
+ * with that custom configuration. One could also use a pre-defined environment and overwrite any properties in the
+ * spring configuration
+ *
+ * @author Ruwan
+ * @since 2.0.0
+ */
+@SuppressWarnings("UnusedDeclaration")
+public class Environment {
+
+    /** Name of the environment */
+    private String name;
+    /** whether binary class reloading (class hot swapping) is enabled or not */
+    private boolean binaryClassReloadingEnabled;
+    /** whether configuration auto refreshing (once changed) is enabled or not */
+    private boolean configAutoRefreshEnabled;
+
+    /**
+     * Constructs an Environment with the given name, while there are pre-defined environments as described in the
+     * header comment, you could build your custom environment too.
+     *
+     * @param name name of the environment, with pre-defined values for "dev", "test", "unit_test", "sample",
+     *             "stage" and "prod"
+     */
+    public Environment(String name) {
+        this.name = name;
+        try {
+            NamedEnv env = NamedEnv.valueOf(name.toUpperCase());
+            binaryClassReloadingEnabled = env.binaryClassReloading();
+            configAutoRefreshEnabled = env.configAutoRefreshing();
+        } catch (IllegalArgumentException ignore) {}
+    }
+
+    /**
+     * Whether binary class reloading or class hot swapping is enabled at the runtime, so that invoking the method
+     * {@link org.adroitlogic.ultraesb.jmx.core.ServerManagementMXBean#addOrUpdateConfigFromFile(String)} to add or
+     * update a configuration will use the re-loaded classes.
+     *
+     * @return true if hot swapping is enabled, otherwise false
+     */
+    public boolean isBinaryClassReloadingEnabled() {
+        return binaryClassReloadingEnabled;
+    }
+
+    /**
+     * Sets whether the binary class reloading or the class hot swapping is enabled at runtime, so that invoking the
+     * method {@link org.adroitlogic.ultraesb.jmx.core.ServerManagementMXBean#addOrUpdateConfigFromFile(String)}
+     * to add or update a configuration will use the re-loaded classes.
+     *
+     * @param binaryClassReloadingEnabled true to enable hot swapping of classes
+     */
+    public void setBinaryClassReloadingEnabled(boolean binaryClassReloadingEnabled) {
+        this.binaryClassReloadingEnabled = binaryClassReloadingEnabled;
+    }
+
+    /**
+     * Whether on the fly configuration updating is enabled. This is usually important in development environments
+     * where the UltraESB is running on an IDE while developing the configurations</p>
+     *
+     * <p>** Note: Not recommended to be used in production environments
+     *
+     * @return true if configuration auto refreshing is enabled, otherwise false
+     */
+    public boolean isConfigAutoRefreshEnabled() {
+        return configAutoRefreshEnabled;
+    }
+
+    /**
+     * Sets whether on the fly configuration updating is enabled. This is usually important in development environments
+     * where the UltraESB is running on an IDE while developing the configurations</p>
+     *
+     * <p>** Note: Not recommended to be used in production environments
+     *
+     * @param configAutoRefreshEnabled true to enable configuration auto refreshing
+     */
+    public void setConfigAutoRefreshEnabled(boolean configAutoRefreshEnabled) {
+        this.configAutoRefreshEnabled = configAutoRefreshEnabled;
+    }
+
+    /**
+     * Gets the name of the environment
+     *
+     * @return name of the environment
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Represent the pre-defined named environments
+     *
+     * @see Environment
+     */
+    public enum NamedEnv {
+        DEV { // typical development environment
+            @Override boolean binaryClassReloading() { return true; }
+            @Override boolean configAutoRefreshing() { return false; }
+        }, IDE { // typical integrated development environment (IDE)
+            @Override boolean binaryClassReloading() { return true; }
+            @Override boolean configAutoRefreshing() { return true; }
+        }, UNIT_TEST { // typical unit testing environment
+            @Override boolean binaryClassReloading() { return false; }
+            @Override boolean configAutoRefreshing() { return false; }
+        }, SAMPLE { // typical sample environment
+            @Override boolean binaryClassReloading() { return false; }
+            @Override boolean configAutoRefreshing() { return false; }
+        }, TEST { // typical test environment (integration)
+            @Override boolean binaryClassReloading() { return true; }
+            @Override boolean configAutoRefreshing() { return false; }
+        }, STAGE { // typical staging environment
+            @Override boolean binaryClassReloading() { return true; }
+            @Override boolean configAutoRefreshing() { return false; }
+        }, PROD { // typical production environment
+            @Override boolean binaryClassReloading() { return true; }
+            @Override boolean configAutoRefreshing() { return false; }
+        };
+
+        abstract boolean binaryClassReloading();
+        abstract boolean configAutoRefreshing();
+    }
+}

modules/core/src/main/java/org/adroitlogic/ultraesb/core/ProxyService.java

         if (inSequence != null && !inSequence.isStarted()) {
             inSequence.setConfig(config);
             inSequence.setChildContext(childContext);
+            inSequence.setClassLoader(classLoader);
             inSequence.setPrepared(true);
             inSequence.start();
         }
         if (outSequence != null && !outSequence.isStarted()) {
             outSequence.setConfig(config);
             outSequence.setChildContext(childContext);
+            outSequence.setClassLoader(classLoader);
             outSequence.setPrepared(true);
             outSequence.start();
         }
         if (errorSequence != null && !errorSequence.isStarted()) {
             errorSequence.setConfig(config);
             errorSequence.setChildContext(childContext);
+            errorSequence.setClassLoader(classLoader);
             errorSequence.setPrepared(true);
             errorSequence.start();
         }

modules/core/src/main/java/org/adroitlogic/ultraesb/core/Sequence.java

                 "org.adroitlogic.ultraesb.api.Message msg, " +
                 "final org.adroitlogic.ultraesb.api.Mediation mediation) throws Exception { " + code + "\n}\n}";
 
-        InMemoryClassLoader mcl = new InMemoryClassLoader(classIdentifier, classSource, id, proxyID);
+        InMemoryClassLoader mcl = new InMemoryClassLoader(classIdentifier, classSource, id, proxyID, classLoader);
         try {
             Class compiledClass = mcl.loadClass(classIdentifier);
             codeSnippetInstance = (JavaCodeSnippetSequence) compiledClass.newInstance();
     }
 
     /**
-     * Create a compiled Java class instance for the given sequence and Java souce code
+     * Create a compiled Java class instance for the given sequence and Java source code
      */
     private void prepareClass() {
 
         logger.debug("Building sequence : {} as a Java class sequence", id);
 
-        InMemoryClassLoader mcl = new InMemoryClassLoader(id, code, id, proxyID);
+        InMemoryClassLoader mcl = new InMemoryClassLoader(id, code, id, proxyID, classLoader);
         try {
             Class compiledClass = mcl.loadClass(id);
             classInstance = (JavaClassSequence) compiledClass.newInstance();
         logger.debug("Building sequence : {} as a Java class sequence using class : {}", id, className);
 
         try {
-            classInstance = (JavaClassSequence) Class.forName(className).newInstance();
+            classInstance = (JavaClassSequence) classLoader.loadClass(className).newInstance();
             classInstance.init(config);
 
             logger.debug("Java class : {} for sequence : {} successfully located and instantiated", className, id);
 
     /**
      * Pickup the specified class from Spring
+     * todo:ruwan: can we support hot swapping for the referenced classes from this
      */
     private void prepareClassFromBean() {
         logger.debug("Building sequence : {} as a Spring bean sequence using bean : {}", id, springBeanName);
 
         logger.debug("Building sequence : {} as a Java file sequence", id);
 
-        InMemoryClassLoader mcl = new InMemoryClassLoader(id, readFileToString(fileName), id, proxyID);
+        InMemoryClassLoader mcl = new InMemoryClassLoader(id, readFileToString(fileName), id, proxyID, classLoader);
         try {
             Class compiledClass = mcl.loadClass(id);
             classInstance = (JavaClassSequence) compiledClass.newInstance();

modules/core/src/main/java/org/adroitlogic/ultraesb/core/compile/HotSwapClassLoader.java

+/*
+ * AdroitLogic UltraESB Enterprise Service Bus
+ *
+ * Copyright (c) 2010-2012 AdroitLogic Private Ltd. (http://adroitlogic.org). All Rights Reserved.
+ *
+ * GNU Affero General Public License Usage
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
+ * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along with this program (See LICENSE.AGPL).
+ * If not, see http://www.gnu.org/licenses/agpl-3.0.html
+ *
+ * Commercial Usage
+ *
+ * Licensees holding valid UltraESB Commercial licenses may use this file in accordance with the UltraESB Commercial
+ * License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written
+ * agreement between you and AdroitLogic.
+ *
+ * If you are unsure which license is appropriate for your use, or have questions regarding the use of this file,
+ * please contact AdroitLogic at info@adroitlogic.com
+ */
+
+package org.adroitlogic.ultraesb.core.compile;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.tools.JavaFileObject;
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.*;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * Key piece of code that injects the reloading of the related ar files for a given dynamic sub context of UltraESB.
+ * This together with on-the-fly sequence compilation enables the Java to act as a dynamic environment for UltraESB.</p>
+ *
+ * <p>The configuration update command {@link org.adroitlogic.ultraesb.ServerManager#addOrUpdateConfigFromFile(String)},
+ * will atomically reload the classes associated with this class loader, which defaults to all the jar files in the
+ * "lib/custom" directory relative to the UltraESB installation directory.</p>
+ *
+ * <p>To guarantee the atomicity it make sure that the previous version of the class exists until the outdated version
+ * of updated configuration file, until it is purged with the method
+ * {@link org.adroitlogic.ultraesb.ServerManager#purgeAllOutdatedConfigs()}. All these configuration administration
+ * commands of the server manager are available for operational users via the UTerm CLI via
+ * {@link org.adroitlogic.ultraesb.jmx.core.ServerManagementMXBean}.</p>
+ *
+ * <p>This class loader takes a copy of the custom jar path to make sure the correctness, for each initialization which
+ * detects a change to the above class path.
+ *
+ * @see org.adroitlogic.ultraesb.ServerManager
+ * @see org.adroitlogic.ultraesb.jmx.core.ServerManagementMXBean
+ * @author Ruwan
+ * @since 2.0.0
+ */
+public class HotSwapClassLoader extends ClassLoader {
+
+    public static final String CUSTOM_JAR_PATH = "lib" + File.separator + "custom";
+    public static final String INSTANCE_JAR_ROOT = "tmp" + File.separator + "lib_temp";
+
+    private static final Logger logger = LoggerFactory.getLogger(HotSwapClassLoader.class);
+    private static final Object lock = new Object();
+    private static final Map<String, String> instanceJarPathHashes = new HashMap<String, String>();
+    private static final Map<String, List<HotSwapClassLoader>> existingLoaders
+            = new HashMap<String, List<HotSwapClassLoader>>();
+
+    private Map<String, Class<?>> swappableClasses = new HashMap<String, Class<?>>();
+    private Map<String, URI> availableDefinitions = new HashMap<String, URI>();
+    private File instanceJarPath;
+    private State state = State.DESTROYED;
+
+    public HotSwapClassLoader() {
+        this(CUSTOM_JAR_PATH, INSTANCE_JAR_ROOT);
+    }
+
+    @SuppressWarnings("ResultOfMethodCallIgnored")
+    public HotSwapClassLoader(String jarPath, String instanceJarRoot) {
+
+        state = State.INITIALIZING;
+        File customJarDir = new File(jarPath);
+
+        try {
+            // make sure that the custom lib directory is not modified while the hot swap class loader is initialized
+            if (!customJarDir.setWritable(false, false)) {
+                logger.warn("Couldn't disable write access to the jar path {}", customJarDir.getAbsolutePath());
+            }
+            String customJarDirHash = calculateDirHash(customJarDir, null);
+            File instanceLibPath;
+            List<HotSwapClassLoader> loaders;
+
+            synchronized (lock) {
+                if (instanceJarPathHashes.keySet().contains(customJarDirHash)) {
+                    instanceJarPath = new File(instanceJarPathHashes.get(customJarDirHash));
+                    instanceLibPath = new File(instanceJarPath, "lib");
+                    loaders = existingLoaders.get(instanceJarPath.getAbsolutePath());
+                    loaders.add(this);
+                } else {
+                    instanceJarPath = new File(instanceJarRoot + File.separator + "swappable_" + Math.abs(new Random().nextInt()));
+                    instanceJarPath.mkdirs();
+                    instanceJarPath.deleteOnExit();
+                    instanceLibPath = new File(instanceJarPath, "lib");
+                    loaders = new ArrayList<HotSwapClassLoader>();
+                    loaders.add(this);
+                    try {
+                        FileUtils.copyDirectory(customJarDir, instanceLibPath, true);
+                        instanceJarPathHashes.put(customJarDirHash, instanceJarPath.getAbsolutePath());
+                    } catch (IOException e) {
+                        logger.warn("Couldn't take a snapshot of the swappable jar files, operating on the original location");
+                        instanceLibPath = customJarDir;
+                    }
+                }
+                existingLoaders.put(instanceJarPath.getAbsolutePath(), loaders);
+                for (String jarName : instanceLibPath.list()) {
+                    loadJarFile(new File(instanceLibPath, jarName));
+                }
+            }
+
+            // register a shutdown hook to clean the swappable jar directory copy
+            Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    destroy();
+                }
+            }));
+
+            state = State.ACTIVE;
+        } finally {
+            if (!customJarDir.setWritable(true, false)) {
+                logger.error("Couldn't revert the writable access of the jar directory {}",
+                        customJarDir.getAbsolutePath());
+            }
+        }
+    }
+
+    /**
+     * Takes the control of loading of the classes in the specified custom jar path, allowing them to be reloaded on
+     * executing {@link org.adroitlogic.ultraesb.core.mgt.ServerManagement#addOrUpdateConfigFromFile(String)}
+     *
+     * @param name the full qualified name (FQN) of the class to be loaded
+     * @return the Class instance of the given name if it is either found in the custom libs or in the system class path
+     * @throws ClassNotFoundException if the class is not found both in the custom jars as well as
+     * in the system class path
+     */
+    @Override
+    public Class<?> loadClass(String name) throws ClassNotFoundException {
+        if (!state.isActive()) {
+            throw new IllegalStateException("Hot-swap class loader is not active");
+        }
+
+        if (swappableClasses.containsKey(name)) {
+            // class is an already loaded swappable class
+            logger.debug("Class {} is a swappable class and it has already bean loaded", name);
+            return swappableClasses.get(name);
+        } else {
+            String definitionName = convertToDefinitionName(name);
+            logger.debug("Derived the definition name of the class {} to be {}", name, definitionName);
+            if (availableDefinitions.containsKey(definitionName)) {
+                // the class is already known to be belonging to the swappable space
+                logger.debug("Class {} is known to be a swappable class", name);
+                return loadSwappableClass(name, definitionName);
+            } else {
+                // class doesn't belong to the swappable space fallback to usual loading
+                logger.debug("Couldn't locate the class {} in the swappable space, loading it as a usual class", name);
+                return loadUsualClass(name);
+            }
+        }
+    }
+
+    /**
+     * List the swappable classes found in the custom jar path, as {@link JavaFileObject}s to be used by
+     * on the fly compilation of the UltraESB configuration sequences.
+     *
+     * @param packageName name of the package to look for classes
+     * @param recursive whether the above package should be recursively checked for internal package classes
+     * @return a list of java file objects matching the given package name in the custom jar path
+     */
+    public List<JavaFileObject> listSwappableClasses(String packageName, boolean recursive) {
+
+        if (!state.isActive()) {
+            throw new IllegalStateException("Hot-swap class loader is not active");
+        }
+
+        List<JavaFileObject> javaFiles = new ArrayList<JavaFileObject>();
+        String packageDef = packageName.replaceAll("\\.", "/");
+        for (String definitionName : availableDefinitions.keySet()) {
+            if (definitionName.startsWith(packageDef)) {
+                if (recursive || !definitionName.substring(packageDef.length() + 1).contains("/")) {
+                    try {
+                        javaFiles.add(new SwappableJavaFileObject(new URI("jar:" +
+                                availableDefinitions.get(definitionName).toString() + "!/" + definitionName)));
+                    } catch (URISyntaxException e) {
+                        logger.warn("Couldn't include the jva class file {} into the swappable classes for the compiler",
+                                definitionName);
+                    }
+                }
+            }
+        }
+        return javaFiles;
+    }
+
+    private Class<?> loadSwappableClass(String className, String definitionName) throws ClassNotFoundException {
+        // it is assumed to be that the class is already available in the available classes map
+        try {
+            JarFile jarFile = new JarFile(new File(availableDefinitions.get(definitionName)));
+            byte[] bytes = IOUtils.toByteArray(jarFile.getInputStream(jarFile.getEntry(definitionName)));
+            Class<?> clazz = defineClass(className, bytes, 0, bytes.length);
+            swappableClasses.put(className, clazz);
+            jarFile.close();
+            logger.debug("Loaded class {} as a swappable class", className);
+            return clazz;
+        } catch (IOException e) {
+            logger.warn("Class {} located as a swappable class, but couldn't be loaded due to : {}, " +
+                    "trying to load the class asa usual class", className, e.getMessage());
+            return loadUsualClass(className);
+        }
+    }
+
+    private Class<?> loadUsualClass(String name) throws ClassNotFoundException {
+        return Thread.currentThread().getContextClassLoader().loadClass(name);
+    }
+
+    private synchronized void loadJarFile(File jar) {
+        try {
+            JarFile jarFile = new JarFile(jar);
+            Enumeration<JarEntry> entries = jarFile.entries();
+            while (entries.hasMoreElements()) {
+                JarEntry entry = entries.nextElement();
+                // we are interested only on class files
+                if (entry.getName().endsWith(".class")) {
+                    availableDefinitions.put(entry.getName(), jar.toURI());
+                }
+            }
+            jarFile.close();
+        } catch (IOException e) {
+            logger.debug("Error in trying to list the jar file {}", jar.getName(), e);
+        }
+    }
+
+    private String convertToDefinitionName(String className) {
+        return className.replaceAll("\\.", "/") + ".class";
+    }
+
+    /**
+     * Destroy this class loader, mainly removes the copy of the custom jar directory if there are no class loaders
+     * except for this referring the same directory path (same version of the custom jars)
+     */
+    public void destroy() {
+        synchronized (lock) {
+            if (state.isActive()) {
+                state = State.DESTROYING;
+                List<HotSwapClassLoader> loaders = existingLoaders.get(instanceJarPath.getAbsolutePath());
+                loaders.remove(this);
+                if (loaders.isEmpty()) {
+                    try {
+                        instanceJarPathHashes.remove(instanceJarPath.getAbsolutePath());
+                        FileUtils.deleteDirectory(instanceJarPath);
+                    } catch (IOException e) {
+                        logger.warn("Couldn't delete the temporary jar path {} due to : {}", instanceJarPath, e.getMessage());
+                    }
+                }
+                state = State.DESTROYED;
+            }
+        }
+    }
+
+    /**
+     * Make sure to call destroy to do the relevant cleanup, in the case of a garbage collection of the class loader
+     * of a purged configuration.
+     *
+     * @throws Throwable in an error in finalization
+     */
+    public void finalize() throws Throwable {
+        try {
+            destroy();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private enum State {
+
+        INITIALIZING, ACTIVE, DESTROYING, DESTROYED;
+
+        public boolean isActive() {
+            return this.equals(State.ACTIVE);
+        }
+    }
+
+    private String calculateDirHash(File dir, String prefix) {
+
+        if (dir == null || !dir.exists() || dir.isFile()) {
+            throw new IllegalArgumentException("Input to calculate the hash doesn't represent a directory");
+        }
+
+        StringBuilder builder = new StringBuilder(prefix != null ? prefix : dir.getAbsolutePath());
+        File[] files = dir.listFiles(new FilenameFilter() {
+            @Override
+            public boolean accept(File dir, String name) {
+                return name.endsWith(".jar");
+            }
+        });
+        if (files != null) {
+            Arrays.sort(files);
+            for (File f : files) {
+                builder.append(f.getAbsolutePath());
+                builder.append(f.lastModified());
+                builder.append(f.length());
+            }
+        }
+        return DigestUtils.md5Hex(builder.toString());
+    }
+}

modules/core/src/main/java/org/adroitlogic/ultraesb/core/compile/InMemoryClassLoader.java

     private static final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
     private static final InMemoryFileManager fileManager = new InMemoryFileManager(compiler);
 
-    public InMemoryClassLoader(String name, String javaSource, String id, String proxyID) {
+    private ClassLoader classLoader;
+
+    public InMemoryClassLoader(String name, String javaSource, String id, String proxyID, ClassLoader classLoader) {
+
+        this.classLoader = classLoader;
+        fileManager.setClassLoader(classLoader);
         List<JavaSource> list = new ArrayList<JavaSource>();
 
         list.add(new JavaSource(name, javaSource, id, proxyID));
         List<String> compilerFlags = new ArrayList<String>();
-        compilerFlags.add("-Xlint:all");    // report all warnings
+        compilerFlags.add("-Xlint:all");
 
         ClassLoader toolClassLoader = ToolProvider.getSystemToolClassLoader();
         if (toolClassLoader != null) {
                         compilerFlags.add(sb.toString());
 
                     } else if (ultraHome != null) {
-                        String libPath = null;
+                        String libPath;
                         if (ultraHome.contains("classes")) {
                             // if WEB-INF/classes is set as root
                             libPath = ultraHome.substring(0, ultraHome.indexOf("classes")) + "lib" + File.separator;
         compilerFlags, null, list).call();
     }
 
+    public Class<?> loadClass(String name) throws ClassNotFoundException {
+        if (fileManager.containsClass(name)) {
+            return super.loadClass(name);
+        }
+        return classLoader.loadClass(name);
+    }
+
     @Override
     protected Class<?> findClass(String name) throws ClassNotFoundException {
 

modules/core/src/main/java/org/adroitlogic/ultraesb/core/compile/InMemoryFileManager.java

 package org.adroitlogic.ultraesb.core.compile;
 
 import javax.tools.*;
-import java.util.Map;
-import java.util.HashMap;
+import java.io.IOException;
+import java.util.*;
 
 /**
- * An in-memory file manager that holds compiled classes as ByteArrayBackedClass instances in a map
+ * An in-memory file manager that holds compiled classes as ByteArrayBackedClass instances in a map.</p>
  *
+ * <p>It also makes sure that the swappable class space is made available to the javac compiler at run time for
+ * on-the-fly compiling of sequence fragments in the UltraESB, both at startup as well as at any configuration
+ * management command such as {@link org.adroitlogic.ultraesb.ServerManager#addOrUpdateConfigFromFile(String)}.
+ *
+ * @see org.adroitlogic.ultraesb.ServerManager
  * @author asankha
+ * @author Ruwan
  */
 public class InMemoryFileManager extends ForwardingJavaFileManager<JavaFileManager> {
     
     private final Map<String, ByteArrayBackedClass> map = new HashMap<String, ByteArrayBackedClass>();
+    private ClassLoader classLoader;
 
     InMemoryFileManager(JavaCompiler compiler) {
         super(compiler.getStandardFileManager(null, null, null));
     }
 
+    /**
+     * Set the class loader to be used by the file manager
+     *
+     * @param classLoader the class loader to be used
+     */
+    public void setClassLoader(ClassLoader classLoader) {
+        this.classLoader = classLoader;
+    }
+
     public ByteArrayBackedClass getClass(String name) {
         return map.get(name);
     }
 
+    /**
+     * Checks whether the given class belongs to this in-memory file manager
+     *
+     * @param name binary name of the class to be looked for
+     * @return true if this class is an in memory on-the-fly compiled class
+     */
+    public boolean containsClass(String name) {
+        return map.containsKey(name);
+    }
+
+    /**
+     * Depending on whether hot swapping is enabled or not gives either an instance of {@link HotSwapClassLoader} or
+     * the current threads context class loader
+     *
+     * @param location {@inheritDoc}
+     * @return if hot swapping is enabled an instance of {@link HotSwapClassLoader}, or {@inheritDoc}
+     */
+    @Override
+    public ClassLoader getClassLoader(Location location) {
+        return classLoader;
+    }
+
     @Override
     public ByteArrayBackedClass getJavaFileForOutput(
         Location location, String name, JavaFileObject.Kind kind, FileObject source) {
         map.put(name, byteArrayBackedClass);
         return byteArrayBackedClass;
     }
+
+    /**
+     * Returns a {@link WrappedIterator} with list of {@link JavaFileObject}s found in the custom jar path and the
+     * system class path (searched by the {@link StandardJavaFileManager})</p>
+     *
+     * <p>Based on the construction of the {@link WrappedIterator} and the behavior of the same iterator implementation
+     * the java file objects found in the custom jar path takes the precedence.
+     *
+     * @param location {@inheritDoc}
+     * @param packageName {@inheritDoc}
+     * @param kinds {@inheritDoc}
+     * @param recurse {@inheritDoc}
+     * @return an {@link org.adroitlogic.ultraesb.core.compile.WrappedIterator.IteratorWrapper} with java file objects
+     * matching the given arguments giving the precedence to the custom jar path
+     * @throws IOException {@inheritDoc}
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds,
+                                         boolean recurse) throws IOException {
+        if (classLoader != null && classLoader instanceof HotSwapClassLoader &&
+                "CLASS_PATH".equals(location.getName()) && kinds.contains(JavaFileObject.Kind.CLASS)) {
+            return new WrappedIterator<JavaFileObject>(((HotSwapClassLoader) classLoader).listSwappableClasses(
+                    packageName, recurse).iterator(),
+                    super.list(location, packageName, kinds, recurse).iterator()).getWrapper();
+        } else {
+            return super.list(location, packageName, kinds, recurse);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String inferBinaryName(Location location, JavaFileObject file) {
+        if (file instanceof SwappableJavaFileObject) {
+            return ((SwappableJavaFileObject) file).toBinaryName();
+        } else {
+            return super.inferBinaryName(location, file);
+        }
+    }
 }

modules/core/src/main/java/org/adroitlogic/ultraesb/core/compile/SwappableJavaFileObject.java

+/*
+ * AdroitLogic UltraESB Enterprise Service Bus
+ *
+ * Copyright (c) 2010-2012 AdroitLogic Private Ltd. (http://adroitlogic.org). All Rights Reserved.
+ *
+ * GNU Affero General Public License Usage
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
+ * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along with this program (See LICENSE.AGPL).
+ * If not, see http://www.gnu.org/licenses/agpl-3.0.html
+ *
+ * Commercial Usage
+ *
+ * Licensees holding valid UltraESB Commercial licenses may use this file in accordance with the UltraESB Commercial
+ * License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written
+ * agreement between you and AdroitLogic.
+ *
+ * If you are unsure which license is appropriate for your use, or have questions regarding the use of this file,
+ * please contact AdroitLogic at info@adroitlogic.com
+ */
+
+package org.adroitlogic.ultraesb.core.compile;
+
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.NestingKind;
+import javax.tools.JavaFileObject;
+import java.io.*;
+import java.net.URI;
+
+/**
+ * Implementation of a {@link JavaFileObject} to make sure the swappable jar files are available for on-the-fly
+ * compilation of the UltraESB sequence fragments.
+ *
+ * @author Ruwan
+ * @since 2.0.0
+ */
+public class SwappableJavaFileObject implements JavaFileObject {
+
+    private final URI uri;
+
+    /**
+     * Construct a SwappableJavaFileObject with the given URI.
+     *
+     * @param uri the URI for this file object
+     */
+    protected SwappableJavaFileObject(URI uri) {
+        this.uri = uri;
+    }
+
+    @Override
+    public Kind getKind() {
+        return Kind.CLASS;
+    }
+
+    @Override
+    public boolean isNameCompatible(String simpleName, Kind kind) {
+        String baseName = simpleName + Kind.CLASS.extension;
+        return Kind.CLASS.equals(kind) && (baseName.equals(toUri().getPath())
+                || toUri().getPath().endsWith("/" + baseName));
+    }
+
+    @Override
+    public NestingKind getNestingKind() {
+        return null;
+    }
+
+    @Override
+    public Modifier getAccessLevel() {
+        return null;
+    }
+
+    @Override
+    public URI toUri() {
+        return uri;
+    }
+
+    @Override
+    public String getName() {
+        String uriString = toUri().toString();
+        String classPart = uriString.substring(uriString.indexOf('!') + 1);
+        return classPart.substring(classPart.lastIndexOf('/') + 1);
+    }
+
+    @Override
+    public InputStream openInputStream() throws IOException {
+        return toUri().toURL().openStream();
+    }
+
+    @Override
+    public OutputStream openOutputStream() throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
+        return new InputStreamReader(openInputStream());
+    }
+
+    @Override
+    public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Writer openWriter() throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public long getLastModified() {
+        return 0L;
+    }
+
+    @Override
+    public boolean delete() {
+        return false;
+    }
+
+    public String toBinaryName() {
+        String uriString = uri.toString();
+        return uriString.substring(uriString.indexOf("!") + 2).replaceAll("/", ".").replaceAll(".class$", "");
+    }
+}

modules/core/src/main/java/org/adroitlogic/ultraesb/core/compile/WrappedIterator.java

+/*
+ * AdroitLogic UltraESB Enterprise Service Bus
+ *
+ * Copyright (c) 2010-2012 AdroitLogic Private Ltd. (http://adroitlogic.org). All Rights Reserved.
+ *
+ * GNU Affero General Public License Usage
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
+ * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along with this program (See LICENSE.AGPL).
+ * If not, see http://www.gnu.org/licenses/agpl-3.0.html
+ *
+ * Commercial Usage
+ *
+ * Licensees holding valid UltraESB Commercial licenses may use this file in accordance with the UltraESB Commercial
+ * License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written
+ * agreement between you and AdroitLogic.
+ *
+ * If you are unsure which license is appropriate for your use, or have questions regarding the use of this file,
+ * please contact AdroitLogic at info@adroitlogic.com
+ */
+
+package org.adroitlogic.ultraesb.core.compile;
+
+import java.util.*;
+
+/**
+ * Provides the ability to create a wrapped iterator combining any number of iterators. This implementation satisfies
+ * the generic {@link Iterator} semantics and the user can safely assume it to be a single iterator.</p>
+ *
+ * <p>You could add more concrete (or even wrapped) iterators into the wrapper while being iterated. Added iterators are
+ * in-order called for items, for any of {@link #next()}, or {@link #hasNext()} methods.</p>
+ *
+ * <p>It is thread safe, however as usual iterators the access is generally done by a single thread sequentially.</p>
+ *
+ * <p>This also exposes the wrapped iterator as an {@link Iterable} via {@link #getWrapper()} method.
+ *
+ * @see Iterator
+ * @author Ruwan
+ * @since 2.0.0
+ */
+public class WrappedIterator<E> implements Iterator<E> {
+
+    List<Iterator<E>> iterators = new ArrayList<Iterator<E>>();
+    int iteratorIndex = 0;
+
+    public WrappedIterator(Iterator<E> ... iterators) {
+        this.iterators.addAll(Arrays.asList(iterators));
+    }
+
+    public synchronized void addIterator(Iterator<E> iterator) {
+        iterators.add(iterator);
+    }
+
+    @Override
+    public synchronized boolean hasNext() {
+        if (iterators.size() == 0) {
+            return false;
+        }
+
+        if (iterators.get(iteratorIndex).hasNext()) {
+            return true;
+        } else if (iteratorIndex < iterators.size() - 1) {
+            iteratorIndex++;
+            return hasNext();
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public synchronized E next() {
+        try {
+            return iterators.get(iteratorIndex).next();
+        } catch (NoSuchElementException e) {
+            if (iteratorIndex < iterators.size() - 1) {
+                iteratorIndex++;
+                return next();
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public void remove() {
+        iterators.get(iteratorIndex).remove();
+    }
+
+    /**
+     * Gets the WrappedIterator as an {@link Iterable}
+     * @return the {@link Iterable} representing this wrapped iterator
+     */
+    public IteratorWrapper<E> getWrapper() {
+        return new IteratorWrapper<E>(this);
+    }
+
+    /**
+     * Gets the WrappedIterator as an {@link Iterable}
+     * @param <T> type of the Iterable
+     */
+    public class IteratorWrapper<T> implements Iterable<T> {
+
+        WrappedIterator<T> iterator;
+
+        IteratorWrapper(WrappedIterator<T> iterator) {
+            this.iterator = iterator;
+        }
+
+        @Override
+        public Iterator<T> iterator() {
+            return iterator;
+        }
+    }
+}

modules/core/src/main/java/org/adroitlogic/ultraesb/core/config/AbstractConfigurationElement.java

     protected String id;
     protected GenericApplicationContext ctx;
     protected ApplicationContext childContext;
+    protected ClassLoader classLoader;
 
     /**
      * This is a temporary name holder for derived IDs used for inline endpoints and sequences. Do not use this
         this.config.defineConfigurationBean(id, this);
     }
 
+    public void setClassLoader(ClassLoader classLoader) {
+        this.classLoader = classLoader;
+    }
+
     protected void throwIllegalStateException(String msg) {
         logger.error(msg);
         throw new IllegalStateException(msg);

modules/core/src/test/java/org/adroitlogic/ultraesb/core/compile/CompileTestUtils.java

+/*
+ * AdroitLogic UltraESB Enterprise Service Bus
+ *
+ * Copyright (c) 2010-2012 AdroitLogic Private Ltd. (http://adroitlogic.org). All Rights Reserved.
+ *
+ * GNU Affero General Public License Usage
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
+ * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along with this program (See LICENSE.AGPL).
+ * If not, see http://www.gnu.org/licenses/agpl-3.0.html
+ *
+ * Commercial Usage
+ *
+ * Licensees holding valid UltraESB Commercial licenses may use this file in accordance with the UltraESB Commercial
+ * License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written
+ * agreement between you and AdroitLogic.
+ *
+ * If you are unsure which license is appropriate for your use, or have questions regarding the use of this file,
+ * please contact AdroitLogic at info@adroitlogic.com
+ */
+
+package org.adroitlogic.ultraesb.core.compile;
+
+import javax.tools.JavaCompiler;
+import javax.tools.ToolProvider;
+import java.io.*;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+/**
+ * Used by hot swapping feature unit testing and UltraESB on the fly code fragment compile feature testing unit.
+ *
+ * NOTE ** Please note that this is a piece of code used for unit testing purposes and this code should not be used for
+ * any production environment.
+ *
+ * This is not a test case, rather a utility used by few test cases
+ *
+ * @author Ruwan
+ * @since 2.0.0
+ */
+@SuppressWarnings({"ResultOfMethodCallIgnored", "ConstantConditions"})
+public class CompileTestUtils {
+
+    public static void compileClassAndCreateJar(File root, String methodName) throws Exception {
+        // source
+        String source = "package test; public class Test { public String " + methodName + "() { return \"test\"; } }";
+
+        // save source in .java file.
+        File sourceFile = new File(root, "src" + File.separator + "test" + File.separator + "Test.java");
+        sourceFile.getParentFile().mkdirs();
+        new FileWriter(sourceFile).append(source).close();
+
+        // compile source file.
+        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+        compiler.run(null, null, null, sourceFile.getPath());
+
+        Manifest manifest = new Manifest();
+        manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
+        JarOutputStream target = new JarOutputStream(new FileOutputStream(new File(root, "test.jar")), manifest);
+        add(new File(root, "src"), target, null);
+        target.close();
+    }
+
+    private static void add(File source, JarOutputStream target, String relativePath) throws IOException {
+        BufferedInputStream in = null;
+        try {
+            if (source.isDirectory()) {
+                if (relativePath != null) {
+                    String name = source.getPath().replace("\\", "/");
+                    if (!name.isEmpty()) {
+                        if (!name.endsWith("/")) {
+                            name += "/";
+                        }
+                        JarEntry entry = new JarEntry(name.substring(relativePath.length() + 1));
+                        entry.setTime(source.lastModified());
+                        target.putNextEntry(entry);
+                        target.closeEntry();
+                    }
+                }
+                for (File nestedFile: source.listFiles()) {
+                    add(nestedFile, target, relativePath != null ? relativePath : source.getPath());
+                }
+                return;
+            }
+
+            JarEntry entry = new JarEntry(source.getPath().replace("\\", "/").substring(relativePath.length() + 1));
+            entry.setTime(source.lastModified());
+            target.putNextEntry(entry);
+            in = new BufferedInputStream(new FileInputStream(source));
+
+            byte[] buffer = new byte[1024];
+            while (true) {
+                int count = in.read(buffer);
+                if (count == -1) {
+                    break;
+                }
+                target.write(buffer, 0, count);
+            }
+            target.closeEntry();
+        }
+        finally {
+            if (in != null) {
+                in.close();
+            }
+        }
+    }
+
+    public static void deleteDir(File rootDir) {
+        for (File f : rootDir.listFiles()) {
+            if (f.isDirectory()) {
+                deleteDir(f);
+            } else {
+                if (!f.delete()) {
+                    System.err.println("Unable to delete " + f.getAbsolutePath());
+                }
+            }
+        }
+        if (!rootDir.delete()) {
+            System.err.println("Unable to delete " + rootDir.getAbsolutePath());
+        }
+    }
+}

modules/core/src/test/java/org/adroitlogic/ultraesb/core/compile/HotSwapClassLoaderTestCase.java

+/*
+ * AdroitLogic UltraESB Enterprise Service Bus
+ *
+ * Copyright (c) 2010-2012 AdroitLogic Private Ltd. (http://adroitlogic.org). All Rights Reserved.
+ *
+ * GNU Affero General Public License Usage
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
+ * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along with this program (See LICENSE.AGPL).
+ * If not, see http://www.gnu.org/licenses/agpl-3.0.html
+ *
+ * Commercial Usage
+ *
+ * Licensees holding valid UltraESB Commercial licenses may use this file in accordance with the UltraESB Commercial
+ * License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written
+ * agreement between you and AdroitLogic.
+ *
+ * If you are unsure which license is appropriate for your use, or have questions regarding the use of this file,
+ * please contact AdroitLogic at info@adroitlogic.com
+ */
+
+package org.adroitlogic.ultraesb.core.compile;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.lang.reflect.Method;
+
+/**
+ * Unit Tests the functionality of the {@link HotSwapClassLoader}
+ *
+ * @author Ruwan
+ * @since 2.0.0
+ */
+public class HotSwapClassLoaderTestCase extends TestCase {
+
+    private File root = new File(System.getProperty("java.io.tmpdir") + File.separator + "sfo-test");
+
+    public void setUp() throws Exception {
+        CompileTestUtils.compileClassAndCreateJar(root, "invokeTest");
+    }
+
+    public void testLoad() throws Exception {
+        String tmpDir = System.getProperty("java.io.tmpdir");
+        HotSwapClassLoader loader = new HotSwapClassLoader(root.getAbsolutePath(), tmpDir);
+        Class test = loader.loadClass("test.Test");
+        Method method = test.getMethod("invokeTest");
+        assertEquals("test", method.invoke(test.newInstance()));
+        new HotSwapClassLoader(root.getAbsolutePath(), tmpDir);
+
+        CompileTestUtils.compileClassAndCreateJar(root, "invokeUpdated");
+        loader = new HotSwapClassLoader(root.getAbsolutePath(), tmpDir);
+        Class test2 = loader.loadClass("test.Test");
+        Method method2 = test2.getMethod("invokeUpdated");
+        assertEquals("test", method2.invoke(test2.newInstance()));
+    }
+
+    public void tearDown() {
+        CompileTestUtils.deleteDir(root);
+    }
+}

modules/core/src/test/java/org/adroitlogic/ultraesb/core/compile/OnTheFlyCompilationTest.java

+/*
+ * AdroitLogic UltraESB Enterprise Service Bus
+ *
+ * Copyright (c) 2010-2012 AdroitLogic Private Ltd. (http://adroitlogic.org). All Rights Reserved.
+ *
+ * GNU Affero General Public License Usage
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
+ * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along with this program (See LICENSE.AGPL).
+ * If not, see http://www.gnu.org/licenses/agpl-3.0.html
+ *
+ * Commercial Usage
+ *
+ * Licensees holding valid UltraESB Commercial licenses may use this file in accordance with the UltraESB Commercial
+ * License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written
+ * agreement between you and AdroitLogic.
+ *
+ * If you are unsure which license is appropriate for your use, or have questions regarding the use of this file,
+ * please contact AdroitLogic at info@adroitlogic.com
+ */
+
+package org.adroitlogic.ultraesb.core.compile;
+
+import junit.framework.TestCase;
+import org.adroitlogic.ultraesb.core.MessageImpl;
+import org.adroitlogic.ultraesb.core.ProxyService;
+import org.adroitlogic.ultraesb.core.Sequence;
+import org.adroitlogic.ultraesb.core.work.SimpleQueueWorkManager;
+
+import java.io.File;
+
+/**
+ * Tests the on-the-fly compilation and execution of the sequences using the hot swap features of classes
+ * referred by the sequence code and sequences itself
+ *
+ * @author Ruwan
+ * @since 2.0.0
+ */
+public class OnTheFlyCompilationTest extends TestCase {
+
+    private File root = new File(System.getProperty("java.io.tmpdir") + File.separator + "sfo-test");
+
+    public void setUp() throws Exception {
+        CompileTestUtils.compileClassAndCreateJar(root, "invokeTest");
+    }
+
+    public void testCompileAndSequenceExecute() throws Exception {
+        String tmpDir = System.getProperty("java.io.tmpdir") + File.separator + "swp_versions";
+        MessageImpl msg = new MessageImpl(true, new ProxyService(), "http");
+        msg.setWorkManager(new SimpleQueueWorkManager(null));
+
+        HotSwapClassLoader loader = new HotSwapClassLoader(root.getAbsolutePath(), tmpDir);
+        Sequence seq = new Sequence();
+        seq.setId("test-seq");
+        seq.setType("java");
+        seq.setCode("msg.addMessageProperty(\"compile.test\", new test.Test().invokeTest());");
+        seq.setClassLoader(loader);
+        seq.start();
+
+        seq.execute(msg);
+        assertEquals(1L, seq.toDetailedView().getSuccessfulCount());
+
+        loader = new HotSwapClassLoader(root.getAbsolutePath(), tmpDir);
+        seq = new Sequence();
+        seq.setId("test-seq");
+        seq.setType("java");
+        seq.setCode("msg.addMessageProperty(\"compile.test\", new test.Test().invokeTest());");
+        seq.setClassLoader(loader);
+        seq.start();
+
+        seq.execute(msg);
+        assertEquals(1L, seq.toDetailedView().getSuccessfulCount());
+
+        CompileTestUtils.compileClassAndCreateJar(root, "invokeUpdated");
+
+        seq.execute(msg);
+        assertEquals(2L, seq.toDetailedView().getSuccessfulCount());
+        assertEquals(1, new File(tmpDir).list().length);
+
+        loader = new HotSwapClassLoader(root.getAbsolutePath(), tmpDir);
+        assertEquals(2, new File(tmpDir).list().length);
+        Sequence seq2 = new Sequence();
+        seq2.setId("test-seq");
+        seq2.setType("java");
+        seq2.setCode("msg.addMessageProperty(\"compile.test2\", new test.Test().invokeUpdated());");
+        seq2.setClassLoader(loader);
+        seq2.start();
+
+        seq2.execute(msg);
+        assertEquals(1L, seq2.toDetailedView().getSuccessfulCount());
+    }
+
+    public void tearDown() {
+        CompileTestUtils.deleteDir(root);
+    }
+}

modules/core/src/test/java/org/adroitlogic/ultraesb/core/compile/WrappedIteratorTestCase.java

+/*
+ * AdroitLogic UltraESB Enterprise Service Bus
+ *
+ * Copyright (c) 2010-2012 AdroitLogic Private Ltd. (http://adroitlogic.org). All Rights Reserved.
+ *
+ * GNU Affero General Public License Usage
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
+ * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along with this program (See LICENSE.AGPL).
+ * If not, see http://www.gnu.org/licenses/agpl-3.0.html
+ *
+ * Commercial Usage
+ *
+ * Licensees holding valid UltraESB Commercial licenses may use this file in accordance with the UltraESB Commercial
+ * License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written
+ * agreement between you and AdroitLogic.
+ *
+ * If you are unsure which license is appropriate for your use, or have questions regarding the use of this file,
+ * please contact AdroitLogic at info@adroitlogic.com
+ */
+
+package org.adroitlogic.ultraesb.core.compile;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+
+/**
+ * Unit Tests the {@link WrappedIterator} functionality
+ *
+ * @author Ruwan
+ * @since 2.0.0
+ */
+@SuppressWarnings("unchecked")
+public class WrappedIteratorTestCase extends TestCase {
+
+    ArrayList<String> list1 = new ArrayList<String>();
+    ArrayList<String> list2 = new ArrayList<String>();
+
+    public void setUp() {
+        list1.add("foo");
+        list2.add("abc");
+        list2.add("xyz");
+    }
+
+    public void testHasNext() {
+        WrappedIterator wit = new WrappedIterator(new ArrayList().iterator(), new ArrayList().iterator());
+        assertFalse(wit.hasNext());
+        wit.addIterator(list1.iterator());
+        assertTrue(wit.hasNext());
+        assertEquals("foo", wit.next());
+        assertFalse(wit.hasNext());
+
+        wit = new WrappedIterator();
+        assertFalse(wit.hasNext());
+
+        wit = new WrappedIterator<String>(list1.iterator(), new ArrayList<String>().iterator());
+        assertTrue(wit.hasNext());
+        assertEquals("foo", wit.next());
+        assertFalse(wit.hasNext());
+    }
+
+    public void testNext() {
+        WrappedIterator<String> wit = new WrappedIterator<String>(list1.iterator(), list2.iterator());
+        assertEquals("foo", wit.next());
+        assertEquals("abc", wit.next());
+        assertEquals("xyz", wit.next());
+        assertFalse(wit.hasNext());
+
+        wit = new WrappedIterator<String>(new ArrayList<String>().iterator(), list2.iterator());
+        assertEquals("abc", wit.next());
+        assertEquals("xyz", wit.next());
+        assertFalse(wit.hasNext());
+        wit.addIterator(list1.iterator());
+        assertTrue(wit.hasNext());
+        assertEquals("foo", wit.next());
+        assertFalse(wit.hasNext());
+    }
+
+    public void testRemove() {
+        WrappedIterator<String> wit = new WrappedIterator<String>(list1.iterator(), list2.iterator());
+        wit.next();
+        wit.remove();
+        assertTrue(wit.hasNext());
+        assertEquals(list1.size(), 0);
+        wit.next();
+        wit.remove();
+        assertEquals(list2.size(), 1);
+    }
+}

resources/RELEASE_NOTES.TXT

 
 Copyright (c) 2010-2012 AdroitLogic Private Ltd. All Rights Reserved
 
-** Changes for 2.0.0-SNAPSHOT ** [xx-xx-2012]
+** Changes for 2.0.0-SNAPSHOT ** [xx-01-2013]
 ------------------------------------
+    - Ability to reload binary jar files and classes in a configuration add or update, guaranteeing the atomicity
+    - Introduction of the runtime environment configurations, such as development, testing, staging and production
+    - File transport enhanced to support local and distributed locking to be able to concurrently poll files
     - AS2 persistence layer has been improved to support multi-tenancy (Refer to Appendix C)
     - UTerm commands made consistent by making all commands subject first (Refer to Appendix D)