JSR88Client

JBoss 8 no longer supports JSR-88 deployment, so this article is no longer relevant. See here: https://issues.jboss.org/browse/AS7-6609

JSR88 client

 

This page shows a sample for basic JSR88 deployment to JBoss. I am just a JBoss user, no guru, so don't expect too much wisdom ;-)

 

The code for the JBoss 4.2 to JBoss 6 deployment is based on the only code snippet I found, a JBoss test: BaseDeploymentTestCase

For JBoss 7, see here (another test case): JSR88DeploymentTestCase.java (the test was renamed during 7.1.2, before the code was found here: EnterpriseDeploymentTestCase.java)

 

For comments, you can mail me at "wolfgang DOT knauf AT gmx DOT de"

 

Workflow

JSR deployment works this way:

  • A valid JEE file (EAR, WAR, JAR) is required somewhere on the local disc.

  • The deployer client has to create a "deployment plan". This is a zip file containing one file "deployment-plan.xml". For a sample see below.

  • Using this deployment plan, the JEE file can be deployed to the server.

  • JBoss 4.2 to 6: The server places it in "$JBOSS_HOME$/server/default/tmp/jsr88"

  • JBoss 7.1: the file is located in a subdirectory of "standalone/data/content": take a look at the "deployments" element in "standalone.xml": here you will find your file, and there an element like this: "<content sha1="8ef9658426749a12b77691ed39bdbfb8ab22d3e1"/>". This is subdirectory name in "data/content": for this sample, it would be "data/content/8e/f9658426749a12b77691ed39bdbfb8ab22d3e1".

  • One important thing for JBoss 4.2 to 6: the app is no longer deployed when the server is restarted. JBoss 7 handles the deployment different, so the app will survive a server restart. But beware not to kill the "content/data" dir.

 

Preparation

For basic functionality, those Jar files are required (please note that this list is based on try and error, and it might change with any release):

 

  • JBoss 4.2:

    • /client/jbossall-client.jar

    • /server/default/lib/dom4j.jar

    • /client/jboss-deployment.jar

    • /server/default/lib/jboss-jsr88.jar

 

  • JBoss 5.0:

    • (up to JBoss 5.0Beta4) /client/jbossall-client.jar

    • (since JBoss 5.0CR1) /client/jboss-javaee.jar

    • /lib/dom4j.jar

    • /client/jboss-deployment.jar

    • (up to JBoss 5.0CR2) /server/default/lib/jboss-jsr88.jar

    • (since JBoss 5.0GA) /common/lib/jboss-jsr88.jar

    • (note: some other Jars might be required if deployment fails and the exceptions are to be de-serialized and shown to the client)

 

  • JBoss 7.1:
    • modules/javax/enterprise/deploy/api/main/jboss-jad-api_*.jar

    • modules/org/jboss/as/ee/deployment/main/jboss-as-ee-deployment-*.jar

    • modules/javax/enterprise/api/main/cdi-api-*.jar

    • modules/org/jboss/logging/main/jboss-logging-*.jar

    • modules/org/jboss/common-core/main/jboss-common-core-*.jar

    • modules/org/apache/log4j/main/log4j-*.jar

    • modules/org/jboss/as/controller-client/main/jboss-as-controller-client-*.jar

    • modules/org/jboss/threads/main/jboss-threads-*.jar

    • modules/org/jboss/as/protocol/main/jboss-as-protocol-*.jar

    • modules/org/jboss/remoting3/main/jboss-remoting-*.jar

    • modules/org/jboss/xnio/main/xnio-api-*.jar

    • modules/org/jboss/dmr/main/jboss-dmr-*.jar

    • modules/org/jboss/xnio/nio/main/xnio-nio-*.jar (to avoid exception "No matching XNIO provider found")           

    • modules/org/jboss/sasl/main/jboss-sasl-*.jar (to avoid error "javax.security.sasl.SaslException: Authentication failed: all available authentication mechanisms failed")

    • modules/org/jboss/marshalling/main/jboss-marshalling-*.jar

    • modules/org/dom4j/main/dom4j-*.jar

 

  • JBoss 7.1 (with Maven): modify your pom.xml as follows to add proper dependencies:
    • add:
      <dependency><groupId>javax.enterprise.deploy</groupId><artifactId>javax.enterprise.deploy-api</artifactId><version>1.6</version></dependency>

       

    • add:
      <dependency><groupId>org.jboss.as</groupId><artifactId>jboss-as-ee-deployment</artifactId><version>7.2.0.Final</version></dependency>

       

    • if your module is a WAR and dom4j's jar wasn't packaged within WEB-INF/lib, add the following to your maven-war-plugin:
      <configuration><archive><manifestEntries><Dependencies>org.dom4j</Dependencies></manifestEntries></archive></configuration>

       

    • when deploying EJB-JARs, be sure to have an ejb-jar.xml must be present (at least with an empty <ejb-jar/> tag) to allow ModuleType recognition

Initialization

JBoss 4.2 to JBoss 6

First of all, you need the JBoss implementation of javax.enterprise.deploy.spi.DeploymentManager. This code finds one by using the deployment factory:

 

import javax.enterprise.deploy.shared.factories.DeploymentFactoryManager;
import javax.enterprise.deploy.spi.DeploymentManager;
import javax.enterprise.deploy.spi.exceptions.DeploymentManagerCreationException;
import javax.naming.Context;
import org.jboss.deployment.spi.DeploymentManagerImpl;
import org.jboss.deployment.spi.factories.DeploymentFactoryImpl;

public static DeploymentManager getDeploymentManager(){

  //Set some environment properties to define the JNDI connection:
  System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
  System.setProperty(Context.URL_PKG_PREFIXES, "org.jboss.naming.client");
  System.setProperty(Context.PROVIDER_URL, "jnp://localhost:1099");

  //JBOSS 5 requires a call to method "DeploymentFactoryImpl.register", otherwise no DeploymentFactories are found.
  //JBoss 4.2 does not know this method.
  DeploymentFactoryImpl.register();

  //Get a DeploymentFactoryManager instance:
  DeploymentFactoryManager dfm = DeploymentFactoryManager.getInstance();

  //Try to get a DeploymentManager from the factory.
  DeploymentManager dm = null;
  try {
    //"DEPLOYER_URI" is "http://org.jboss.deployment/jsr88":
    dm = dfm.getDeploymentManager(DeploymentManagerImpl.DEPLOYER_URI, null, null);
  } catch (DeploymentManagerCreationException e) {
    System.err.println ("Could not get DeploymentManager: " + e.toString());
  }

  return dm;
}

 

 

Important:

  • JBoss 5.0: The deployment factory implementation in JBoss 5.0 cannot be found without the call "org.jboss.deployment.spi.factories.DeploymentFactoryImpl.register();".

  • JBoss 4.2: this method is not present, deployment manager factory is found without any special actions.

 

JBoss 7.1

The basic approach is the same as for previous JBoss versions with these changes:

  • some namespaces changed.
  • no InitialContext required, the DeploymentFactoryManager URL has to contain servername and port (attributes "serverHost" and "serverPort")
  • URL has to contain parameter "targetType=as7"

 

import javax.enterprise.deploy.shared.factories.DeploymentFactoryManager;
import javax.enterprise.deploy.spi.DeploymentManager;
import javax.enterprise.deploy.spi.exceptions.DeploymentManagerCreationException;
import org.jboss.as.ee.deployment.spi.DeploymentManagerImpl;
import org.jboss.as.ee.deployment.spi.factories.DeploymentFactoryImpl;

public static DeploymentManager getDeploymentManager(String _strServer, String _strPort) {
    DeploymentFactoryImpl.register(); //Register for AS7!
    DeploymentFactoryManager dfm = DeploymentFactoryManager.getInstance();

    DeploymentManager dm = null;
    try {
      //"DEPLOYER_URI" is "http://org.jboss.as.ee.deployment/jsr88"
      String sURL = DeploymentManagerImpl.DEPLOYER_URI + "?targetType=as7&amp;serverHost=" + _strServer + "&amp;serverPort=" + _strPort;
      dm = dfm.getDeploymentManager(sURL, null, null);
    } catch (DeploymentManagerCreationException e) {
      System.err.println ("Could not get DeploymentManager: " + e.toString());
    }

    return dm;
}

 

Deploying a JEE file

This method "jsr88Deployment" deploys a file. For this, it needs a deployment plan. With the use of this deployment plan, the application is first distributed to the server, then started.

JBoss 4.2 to JBoss 6

 

import java.io.File;

import javax.enterprise.deploy.shared.StateType;
import javax.enterprise.deploy.spi.DeploymentManager;
import javax.enterprise.deploy.spi.Target;
import javax.enterprise.deploy.spi.TargetModuleID;
import javax.enterprise.deploy.spi.status.DeploymentStatus;
import javax.enterprise.deploy.spi.status.ProgressObject;

//...

/**
 * Deploy a file using a deployment manager.
 *
 * @param strPath Path to the file, e.g. "c:\temp"
 * @param module  The file name, e.g. "MyApp.ear"
 * @param manager This DeploymentManager is used for deployment.
 * @return A ProgressObject, or NULL is deployment failed because of errors in the file.
 * @throws Exception Any errors (no good style ;-) )
 */
private static ProgressObject jsr88Deployment(String strPath, String module, DeploymentManager manager) throws Exception {
  // Get the deployment manager and the distribution targets
  Target[] targets = manager.getTargets();

  //Build a deployment plan (which is a zip file in the temp dir):
  File deploymentPlan = createDeploymentPlan(module);

  // Deploy the specified file. 
  // Step 1: distribute it to the server:
  File moduleArchive = new File(strPath, module);
  ProgressObject progress = manager.distribute(targets, moduleArchive, deploymentPlan);
  DeploymentStatus status = progress.getDeploymentStatus();
  if (status.getState() == StateType.FAILED)
  {
    //Distribution may fail, if e.g. "ejb-jar.xml" is broken!
    //"status.getMessage()" might throw a "ClassNotFoundException", if the exceptions from the server are in Jars
    //which are not included by this client app.
    System.out.println ("Deployment failed: " + status.getMessage());
    return null;
  }

  // Now wait for completion of distribution:
  waitForCompletion(status);

  // Step 2: Start the modules whose IDs are returned by the "distribute" operation.:
  TargetModuleID[] moduleIDs = progress.getResultTargetModuleIDs();
  progress = manager.start(moduleIDs);
  status = progress.getDeploymentStatus();

  //Wait for completion of the "start" operation:
  waitForCompletion(status);

  return progress;
}

 

 

For deployment, we need a "deployment plan", which is a zip file, containing one file "deployment-plan.xml". The code for creating it looks like this:

 

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.util.jar.JarOutputStream;

import org.jboss.deployment.spi.DeploymentMetaData;
import org.jboss.deployment.spi.JarUtils;

//...

/**Creates a deployment plan (zip file) in the temp dir.
 * 
 * @param deploymentFile This is the file to be deployed (full path)
 * @return The file which contains the deployment plan
 * @throws Exception All exceptions...
 */
private static File createDeploymentPlan(String deploymentFile) throws Exception {
  // Create a temp zip file for deployment plan
  File deploymentPlan = File.createTempFile("deploymentplan", ".zip");
  deploymentPlan.deleteOnExit();

  JarOutputStream jos = new JarOutputStream(new FileOutputStream(deploymentPlan));

  // Setup deployment plan meta data with proprietary descriptor
  DeploymentMetaData metaData = new DeploymentMetaData(deploymentFile);

  // Add the meta data to the deployment plan
  String metaStr = metaData.toXMLString();

  JarUtils.addJarEntry(jos, DeploymentMetaData.ENTRY_NAME, new ByteArrayInputStream(metaStr.getBytes()));
  jos.flush();
  jos.close();

  return deploymentPlan;
}

 

 

This is the content of "deployment-plan.xml" in the zip file:

 

<?xml version="1.0" encoding="UTF-8"?>
<jboss-deployment-plan>
  <deployment-name>c:/temp/Stateless.ear</deployment-name>
  <!--Note, deployment-entry elements are not used by the DeploymentManager-->
  <!--The DeploymentManager relies on the the entry nameing convention-->
</jboss-deployment-plan>

 

 

The last helper method is one for waiting until an operation (distribute, start, stop) finishes:

 

import javax.enterprise.deploy.shared.StateType;
import javax.enterprise.deploy.spi.status.DeploymentStatus;

/**
 * Wait for completion of a DeploymentStatus (wait as long as the "StateType" is "RUNNING")
 * 
 * @param status This is the DeploymentStatus on whose completion we wait.
 * @throws InterruptedException
 */
public static void waitForCompletion(DeploymentStatus status) throws InterruptedException {
  // wait for the deployment to finish
  while (StateType.RUNNING == status.getState()){
    Thread.sleep(100);
  }
}

 

 

 

Now we can call the JSR88 deployer with this code snippet:

 

  //Get a Deployment manager:
  DeploymentManager dm = getDeploymentManager();
  if (dm == null) {
    return;
  }

  //Deploy file:
  ProgressObject po = JSR88Deployer.jsr88Deployment("c:\temp\", "Stateless.ear", dm);

  //Just some debugging code: output module IDs of deployed code (progress object is only returned on success):
  if (po != null) {
    System.out.println ("Deployed file. ModuleIDs: ");
    TargetModuleID[] modules = po.getResultTargetModuleIDs();
    for (int intIndexModule = 0; intIndexModule < modules.length; intIndexModule++) {
      System.out.println ("\t" + modules[intIndexModule].toString());
    }
  }

 

 

JBoss 7.1

The code for deployment is the same as above, only two packages of JBoss specific classes have changed:

 

import org.jboss.as.ee.deployment.spi.DeploymentMetaData;
import org.jboss.as.ee.deployment.spi.JarUtils;

 

 

Undeploying

Undeployment is even simpler. We tell the DeploymentManager to stop the module, then undeploy it. Here there's no difference beetween the JBoss version 4.2 to 7.1.

 

import javax.enterprise.deploy.shared.StateType;
import javax.enterprise.deploy.spi.DeploymentManager;
import javax.enterprise.deploy.spi.TargetModuleID;
import javax.enterprise.deploy.spi.status.DeploymentStatus;
import javax.enterprise.deploy.spi.status.ProgressObject;

/**Undeploys a list of modules from the server.
 * 
 * @param resultTargetModuleIDs An array of module IDs
 * @param dm  The DeploymentManager used to communicate with the server
 * @throws Exception Any errors....
 */
private static void jsr88Undeploy(TargetModuleID[] resultTargetModuleIDs, DeploymentManager dm) throws Exception {
  // Step 1: stop application:
  ProgressObject progress = dm.stop(resultTargetModuleIDs);
  DeploymentStatus status = progress.getDeploymentStatus();
  waitForCompletion(status);

  // Check that the app is stopped:
  if (StateType.COMPLETED != status.getState()) {
    System.out.println("Progress.getState != COMPLETED: " + status.getMessage());
    return;
  }

  // Step 2: undeploy:
  progress = dm.undeploy(resultTargetModuleIDs);
  status = progress.getDeploymentStatus();
  waitForCompletion(status);
  //Check the app is undeployed:
  if (StateType.COMPLETED != status.getState()) {
    System.out.println("Progress.getState != COMPLETED: " + status.getMessage());
  }
}

 

 

 

The only problem here is that we need the module ids for undeployment. The easiest way is to use the result of the deployment method, as the javax.enterprise.deploy.spi.status.ProgressObject contains the ids of the deployed modules:

 

  //Deploy file:
  ProgressObject po = JSR88Deployer.jsr88Deployment("c:\temp\", "Stateless.ear", dm);
  jsr88Undeploy(progress.getResultTargetModuleIDs(), dm);

 

 

But in most cases, the app should not be undeployed directly after deployment ;-). So my suggestion is to scan all deployed modules for a specific filename (of course this may result in errors if the file name is not unique). The following snippet scans for a file "Stateless.ear":

 

import javax.enterprise.deploy.shared.ModuleType;
import javax.enterprise.deploy.spi.DeploymentManager;
import javax.enterprise.deploy.spi.Target;
import javax.enterprise.deploy.spi.TargetModuleID;

//...

  //Get the deployment manager:
  DeploymentManager dm = getDeploymentManager();
  if (dm == null) { return; }

  // Find the module for the file:
  Target[] targets = dm.getTargets();
  //We have an EAR file here, so our module type is "EAR:
  String strFileName = fileDeploy.getName();
  ModuleType moduleType = ModuleType.EAR;

  //Get all deployed EAR files.
  TargetModuleID[] modules = dm.getAvailableModules(moduleType, targets);
  // Module list may be null if nothing is deployed.
  if (modules != null) {
    TargetModuleID moduleIDUndeploy = null;
    //Loop over the module IDs until we find a matching one:
    for (int intModule = 0; intModule < modules.length &amp;&amp; moduleIDUndeploy == null; intModule++) {
      //Debugging output:
      //System.out.println("Module " + intModule + ": " + modules[intModule] + ", ModuleID: " + modules[intModule].getModuleID());
      //Check module id against file name (in my sample this is "Stateless.ear"):
      if (modules[intModule].getModuleID().endsWith("Stateless.ear")) { // found
        moduleIDUndeploy = modules[intModule];
      }
    }

    // Undeploy module if found.
    if (moduleIDUndeploy != null) {
      jsr88Undeploy(new TargetModuleID[] { moduleIDUndeploy }, dm);
      System.out.println ("Module with ID '" + moduleIDUndeploy.toString() + "' undeployed");
    } else {
      System.out.println ("File 'Stateless.ear' not deployed");
    }
  }

 

 

Note: DeploymentManager.getAvailableModules returns only the modules which are deployed through JSR88, but no other files which are hot deployed!

 

Logging

JBoss 5.0GA introduced Log4J logging in the deployer code (at least this was the first version where I saw a warning about missing Log4J configuration). So I suggest this log4j.xml:

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="false">
   <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
      <!--This line is for JBoss 5.x-->
      <errorHandler class="org.jboss.logging.util.OnlyOnceErrorHandler"/>
      <!--As of JBoss 6.0, the org.jboss.logging.util.OnlyOnceErrorHandler is no longer present!-->
      <!--<errorHandler class="org.apache.log4j.helpers.OnlyOnceErrorHandler"/>-->
      <param name="Target" value="System.out"/>
      <param name="Threshold" value="INFO"/>

      <layout class="org.apache.log4j.PatternLayout">
         <param name="ConversionPattern" value="%d{ABSOLUTE} %-5p [%c{1}] %m%n"/>
      </layout>
   </appender>

   <category name="org.jboss">
      <!--<priority value="INFO"/>-->
      <priority value="OFF"/>
   </category>

   <root>
      <appender-ref ref="CONSOLE"/>
   </root>

</log4j:configuration>

 

 

It directs all output to the console. I switched off logging of the category "org.jboss", because I always saw a logging entry of ERROR priority, though the code seemed to work fine.