Saturday, September 5, 2009

Deploying to Geronimo from your Netbeans Project

I used to be a major Eclipse fan. I thought Netbeans was useless and featureless. It certain has less Java editing features compared to Eclipse, which has brilliant ones like the Java Search and it's refactoring functions, but it's got a much better design and is more rounded off. So it's not as messy and over done as Eclipse tends to be. Take for an example Eclipse's RCP framework. If you've ever done RCP programming in Eclipse you would have noticed that they went too far in making things declarative and this leads to inconsistencies sometimes, like when you want to add menu items. In some cases you have can do it declarative but in certain cases you need to do it with code, and in even other cases you need to mix the 2.

I prefer to keep things consistent. This allows you to document things more clearly and for new programmers (and even yourself in a few months) to not waste too much time getting up to speed with every small things he runs into. The Netbeans RCP is brilliant. It's got a clean design and layout, and integrates well into the IDE's other features like the JavaEE Application Client and GUI builder. Further Netbeans specialized plugins are maybe not an attempt at long features lists, but they're stable and do their purpose well. They offer everything you need to complete your task. Eclipse tends to have so many features, that none of them are production quality yet. And they tried to abstract things to such a degree, that certain features feel "loose" and not well integrated.

But the point is not to compare Netbeans and Eclipse.

Customizing the Netbeans Build Process
Eclipse has a Geronimo plugin, something which Netbeans does not have yet. There are some projects for Geronimo Netbeans plugins, but they're all dead. It does seem like the 6.8 Netbeans will have the ability to add a Geronimo server though. But until then you need to make other plans when trying to integrate the Geronimo deploy process into your Netbeans projects.

Netbeans has another great feature which makes this easier. In Netbeans your projects are built with ant. The IDE generates a file called "nbproject/build-impl.xml", and this is where all the build logic is found. Though in the root of your project you will find the standard "build.xml". It's this build.xml file which includes the generated build file. To allow for build customization, the generated build file has a couple of empty ant targets, which you can override in your build.xml. There are pre/post targets for all the different tasks performed by the user, such as clean, compile, package, etc. You can also override the main build targets, though you would have to implement them if you don't want to break them.

So, if you wanted to do obfuscate your class files, you can override the "-post-compile" target, and run the obfuscate task, like so:
        <target name="-post-compile">
          <obfuscate>
              <fileset dir="${build.classes.dir}">
              </fileset>
          </obfuscate>
        </target>
So, to integrate into the Geronimo deploy process, we will modify our build.xml. When you create your project, just create it with any "local" server. I use Glassfish 2 since it shipped with my Netbeans download. We will not be using Glassfish, since we're overriding the behaviour of the ant targets. When Netbeans deploys a project it executes the "run-deploy" task, so this is where we'll hook into. Also note that these instruction will only work on Linux (or other Unixes), since I have an "isdeployed.sh" script to make it easier to check if an application is deployed. I'll find a more portable way to do it at another time, and if you do it, please send along your changes. For the mean time though, I did add instructions to the end to get a more limited deployment for those users who aren't developing on *nix.

Deploying to Geronimo
First step is to create a file called "geronimo-ant-deploy.xml" next to your "build.xml", and give it the following contents:
<?xml version="1.0" encoding="UTF-8"?>
<project name="geronimo-ant-deploy" default="geronimo-init" basedir=".">
  <dirname property="basedir.geronimo-ant-deploy" file="${ant.file.geronimo-ant-deploy}"/>

  <property file="nbproject/project.properties"/>
  <property file="${basedir.geronimo-ant-deploy}/geronimo-ant-deploy.properties"/>

  <target name="geronimo-init">
    <property name="geronimo.deploy-name" value="${deploy.moduleId} (${deploy.module})"/>

    <condition property="geronimo.init.dependencies-satisfied">
      <and>
        <isset property="deploy.plan" />
        <isset property="deploy.module" />
        <isset property="deploy.moduleId" />
      </and>
    </condition>

    <condition property="geronimo.is-deployed">
      <and>
        <isset property="deploy.disable-deploy-check"/>
        <equals arg1="${deploy.disable-deploy-check}" arg2="deployed"/>
      </and>
    </condition>

    <fail unless="geronimo.init.dependencies-satisfied">
      Failed to deploy:
      1. You need to set the deploy.plan property to the path to the deploy plan .xml file 
         (Current Value: ${deploy.plan})
      2. You need to set the deploy.module property to the path to the module (archive) to deploy 
         (Current Value: ${deploy.module})
      3. You need to set the deploy.moduleId property to the Geronimo module ID for this project.
         You can find the required value for this in ${deploy.plan} (deploy.plan) as the <dep:moduleId> tag,
         and it would be in the form: groupId/artifactId/version/type
    </fail>
  </target>

  <target name="geronimo-check-deployed" depends="geronimo-init" unless="deploy.disable-deploy-check" 
      description="Checks if the app has been deployed">
    <echo>Checking if module ${geronimo.deploy-name} is already deployed</echo>
    <exec dir="${geronimo.home}" executable="${geronimo.bin}/${geronimo.isdeployed}" 
        resultproperty="geronimo.check-module">
      <arg line="${deploy.moduleId} --user ${geronimo.user} --password ${geronimo.pwd}"/>
    </exec>

    <condition property="geronimo.check-succeeded">
      <or>
        <equals arg1="${geronimo.check-module}" arg2="255"/>
        <equals arg1="${geronimo.check-module}" arg2="0"/>
      </or>
    </condition>

    <fail unless="geronimo.check-succeeded">
      Failed to check if module ${geronimo.deploy-name} is already deployed
    </fail>

    <condition property="geronimo.is-deployed">
      <equals arg1="${geronimo.check-module}" arg2="0"/>
    </condition>
  </target>

  <!-- UNDEPLOY -->
  <target name="geronimo-not-undeploy" depends="geronimo-check-deployed" unless="geronimo.is-deployed" 
      description="Handles the case of the application not being deployed when trying to undeploy">
    <echo>Module is not currently deployed. Not undeploying module ${geronimo.deploy-name}.</echo>
  </target>

  <target name="geronimo-do-undeploy" depends="geronimo-check-deployed" if="geronimo.is-deployed" 
      description="Undeploying webapp in geronimo action">
    <echo>Undeploying ${geronimo.deploy-name}</echo>

    <exec dir="${geronimo.home}" executable="${geronimo.bin}/${geronimo.deploy}" failonerror="true">
      <arg line="--user ${geronimo.user} --password ${geronimo.pwd} undeploy ${deploy.moduleId}"/>
    </exec>

    <echo>Undeployed ${geronimo.deploy-name}</echo>
  </target>

  <target name="geronimo-undeploy" 
      depends="geronimo-init,geronimo-check-deployed,geronimo-not-undeploy,geronimo-do-undeploy" 
      description="Undeploying webapp in geronimo entry point">
  </target>

  <!-- DEPLOY -->
  <target name="geronimo-newdeploy" depends="geronimo-check-deployed" unless="geronimo.is-deployed" 
      description="Deploying new webapp in geronimo action">
    <echo>Not deployed, deploying new application ${geronimo.deploy-name}</echo>

    <exec dir="${geronimo.home}" executable="${geronimo.bin}/${geronimo.deploy}" failonerror="true">
      <arg line="--user ${geronimo.user} --password ${geronimo.pwd} deploy ${basedir}/${deploy.module} ${basedir}/${deploy.plan}"/>
    </exec>

    <echo>Deployed ${geronimo.deploy-name}</echo>
  </target>

  <target name="geronimo-redeploy" depends="geronimo-check-deployed" if="geronimo.is-deployed" 
      description="Redeploying webapp in geronimo action">
    <echo>Already deployed, redeploying ${geronimo.deploy-name}</echo>

    <exec dir="${geronimo.home}" executable="${geronimo.bin}/${geronimo.deploy}" failonerror="true">
      <arg line="--user ${geronimo.user} --password ${geronimo.pwd} redeploy ${basedir}/${deploy.module} ${basedir}/${deploy.plan}"/>
    </exec>

    <echo>Deployed ${geronimo.deploy-name}</echo>
  </target>

  <target name="geronimo-deploy" 
      depends="geronimo-init,geronimo-check-deployed,geronimo-newdeploy,geronimo-redeploy" 
      description="Deploying webapp in geronimo entry point">
  </target>
</project>
Then, in the same directory, create a file called "geronimo-ant-deploy.properties", and give it the following contents (updating them to suite your installation):
kms.home=/opt
geronimo.home=${kms.home}/server/geronimo
geronimo.url=http://localhost:8080/
geronimo.bin=${geronimo.home}/bin
geronimo.lib=${geronimo.home}/lib
geronimo.user=system
geronimo.pwd=manager
geronimo.start=startup.sh
geronimo.stop=shutdown.sh
geronimo.deploy=deploy.sh
geronimo.isdeployed=isdeployed.sh
Lastly, in your Geronimo installation directory (the one listed in geronimo.home above), create a file called "isdeployed.sh" in the "bin" subdirectory, with the following contents:
#!/bin/bash

if [ "$2" = "" ]; then
  echo "Usage: $0 <moduleName> <deploy.sh arg1>..<deploy.sh argN>" >&2
  echo "After the module name you wish to check, you need to supply any other options you wish to " >&2
  echo "Pass onto the deploy.sh script (like the username/password)" >&2
  exit 2
fi

MODULE="$1"
WD=`dirname $0`
DEPLOY="$WD/deploy.sh"

shift

if [ "$MODULE" = "list" ]
then
  "$DEPLOY" "$@" list-modules
  exit 0
fi

if "$DEPLOY" "$@" list-modules | egrep -q "^  . $MODULE"
then
  exit 0
else
  exit 255
fi
Remember to make the isdeployed.sh script executable. You can do so by running from the command prompt: chmod a+x /path/to/geronimo/bin/isdeployed.sh

So now that you've setup the basics for Geronimo deployment you need to configure your project. First thing you need to do is change your build.xml file to the following:
<project name="MyEJBProject" default="default" basedir="." 
    ejbjarproject="http://www.netbeans.org/ns/j2ee-ejbjarproject/3">
  <description>Builds, tests, and runs the project MyEJBProject.</description>

  <import file="nbproject/build-impl.xml"/>
  <import file="geronimo-ant-deploy.xml"/>

  <target name="run-deploy" depends="dist,geronimo-deploy">
  </target>
</project
As you can see above, we include the geronimo-ant-deploy.xml (which has the necessary targets for deploying to Geronimo), and then we override "run-deploy" and configure it to first run "dist" so the JAR is built, and the the "geronimo-deploy" target.

Finally we need to configure the Geronimo deployment by setting some properties in your project. In the project's "nbproject" subdirectory, open the "project.properties" file. Then at the end add the following 3 properties:
deploy.module=${dist.jar}
deploy.plan=${meta.inf}/geronimo-ejb.xml
deploy.moduleId=user/MyEJBProject/1.0/jar
These properties are basically described as follows:
  • deploy.module - the path to that JAR archive you wish to deploy
  • deploy.plan - the path to your Geronimo deploy plan .xml file
  • deploy.moduleId - the module ID of your project. This is needed to check if a module is deployed or not. This is basically the moduleId as defined in your deploy plan, taking the form of: group/artifact/version/type
Once you've completed these steps you can use the standard Netbeans "Deploy" action to deploy your project to a local Geronimo instance.

Modifying for using in Windows
Since we need the "isdeployed.sh" script to check if a project is deployed, you can't run the above on Windows "as-is". You do have a couple of options though.

1. The easiest is probably to use the following "geronimo-ant-deploy.xml" file instead of the one given above:
<project name="geronimo-ant-deploy" default="geronimo-init" basedir=".">
  <dirname property="basedir.geronimo-ant-deploy" file="${ant.file.geronimo-ant-deploy}">

  <property file="nbproject/project.properties">
  <property file="${basedir.geronimo-ant-deploy}/geronimo-ant-deploy.properties">

  <target name="geronimo-init">
    <property name="geronimo.deploy-name" value="${deploy.moduleId} (${deploy.module})">

    <condition property="geronimo.init.dependencies-satisfied">
      <and>
        <isset property="deploy.plan">
        <isset property="deploy.module">
        <isset property="deploy.moduleId">
      </isset>
    </isset>

    <fail unless="geronimo.init.dependencies-satisfied">
      Failed to deploy:
      1. You need to set the deploy.plan property to the path to the deploy plan .xml file
         (Current Value: ${deploy.plan})
      2. You need to set the deploy.module property to the path to the module (archive) to deploy 
         (Current Value: ${deploy.module})
      3. You need to set the deploy.moduleId property to the Geronimo module ID for this project.
         You can find the required value for this in ${deploy.plan} (deploy.plan) as the <dep:moduleId> tag,
         and it would be in the form: groupId/artifactId/version/type
    </fail>
  </isset>

  <!-- UNDEPLOY -->
  <target name="geronimo-undeploy" depends="geronimo-init" 
      description="Undeploying webapp in geronimo entry point">
    <echo>Undeploying ${geronimo.deploy-name}</echo>

    <exec dir="${geronimo.home}" executable="${geronimo.bin}/${geronimo.deploy}" failonerror="true">
      <arg line="--user ${geronimo.user} --password ${geronimo.pwd} undeploy ${deploy.moduleId}">
    </arg>

    <echo>Undeployed ${geronimo.deploy-name}</echo>
  </exec>

  <!-- DEPLOY -->
  <target name="geronimo-deploy" depends="geronimo-undeploy" 
      description="Deploying webapp in geronimo entry point">
    <echo>Deploying new application ${geronimo.deploy-name}</echo>

    <exec dir="${geronimo.home}" executable="${geronimo.bin}/${geronimo.deploy}" failonerror="true">
      <arg line="--user ${geronimo.user} --password ${geronimo.pwd} deploy ${basedir}/${deploy.module} ${basedir}/${deploy.plan}">
    </arg>

    <echo>Deployed ${geronimo.deploy-name}</echo>
  </exec>
  </target>
</project>

2. Alternatively, to keep the conditional deploy/redeploy functionality might be desired, because they a redeploy executes in a single step. For this you can make your own Windows version of "isdeployed.sh" and then modify the appropriate targets in the build file to use your's instead. You can leave the script almost intact by using the Windows version of egrep. Though I'm not sure how you would return success/failure/error levels to ant. At the very least you can possibly return success/failure, which should be sufficient. For this you just need to check for "0" return code as "project is deployed" and all non-zero return codes as "not deployed".
My version above has 3 return levels which is "0" for "deployed", "255" for "not deployed" and any other code for "error". In case of an "error" I would cease the build process with a <fail> task, though this is not crucial for getting the script to run.

3. You can also use Cygwin to run the script inside a "virtual Unix" environment.

So, this is how I manage my Geronimo projects through Netbeans. If you get stuck, drop me a message and I'll see where I can help out.

2 comments:

Sokol said...

Quintin, this article really helped me. Although I have some errors with "exec" ant task, war archieve is now deployed automatically. I am using Geronimo with Tomcat. Do you know if I can implement standart Ant tasks for deploying to standalone Tomcat in that way?

Quintin said...

Tomcat deploy's are fairly simple if you do the Hot Deploy by copying the WAR into it's webapps directory. Tomcat will handle the rest. For an undeploy you can just delete it, and for a redeploy just overwrite it.

Beyond this you should be able to take any Ant code that deploys to Tomcat (search the web, I'm sure there are many) and just replace the relevant sections of my example.