My favorites | Sign in
Project Logo
                
Search
for
Updated May 06, 2009 by ashpub...@mac.com
Labels: Featured
DemoJbossProject  
a real world example with jboss

Jboss Demonstration Project Overview

The serverdemo sample project shows how ProtoJ can be used to develop with jboss. In this example you will be configuring a clean download of jboss. Then instead of starting it in the same project (which you can), you will create a distribution (executable jar) that represents a snapshot of the project. You can then copy and expand it as many time as you like on as many machines as you like and start the jboss server from the expanded project. Serverdemo has been designed to demonstrate a number of use-cases:


Platform independence. Serverdemo is written in 100% pure java 5 and should therefore run on any compatible environment. Even the OS specific bash and windows launch scripts contain just a single call to java with embedded calls to the ant API, making their differences much easier to grapple with. Not only that, all sample projects are tested on Linux (Ubuntu), Unix (Mac OS X) and Windows (XP) during each ProtoJ release.


Jboss virtual machine local configuration. The JAVA_OPTS environment variable is provided by jboss for configuring elements of its virtual machine and is typically used for adding debug capabilities and fine tuning memory and performance settings. With the best of intentions the string manipulation required to hook into this in the bash and windows launch scripts or even ant xml code can be challenging and error-prone. However with the ProtoJ mantra of ensuring every use-case is accessible from java code, this ends up being almost trivial to implement.


Jboss server instance local configuration. In a scripted environment coordinating unique configurations of server instances can add yet another level of difficulty. ProtoJ comes built in with the ability to create a project distribution at any time in the form of a no-arg executable jar. And when combined with its ability to configure a project instance with stored profiles, the result is a powerful way of creating as many project instances as required, each with tailored configurations such as port numbers.


ProtoJ profiles. The local configuration use-cases described above are implemented by ProtoJ profiles, which are just copies of the entire application directory structure. Then when the configure use-case is executed, the profile directory is copied over the top of the application directory with ${var} tokens optionally replaced and properties files merged. A directory configuration use-case is available for working with profiles stored on mounted shares and an scp configuration use-case is available for working with profiles stored elsewhere on the network. Used in conjunction with a backup policy, the scp configuration feature is really handy for implementing persistent application configuration.


Jboss global configuration. Another often problematic area related to configuration is patching a third party product with custom content, which is different than configuration as described above as it relates to content that must never change for different installation instances. Serverdemo makes use of ProtoJ's ability to extract resources from the classpath to a directory location, so that it can provide custom properties-service.xml and jboss-service.xml replacements when it is asked to expand the jboss zip file. Additionally ProtoJ integrates with velocity so that any resource can include velocity mark-up for handling the most challenging of scenarios. For those unfamiliar with velocity this means that the project domain objects can all be accessed from text files in a similar fashion to jsp.


Jboss server validation. Serverdemo provides the ability to start a jboss instance and detect whether or not it is successful. This is another use-case that is straight forward to implement due to the unified java code-base. In a scripted environment this might be achieved in a platform independent way with an ant launch script and a custom ant task. However this sort of approach tends to result in islands of functionality driven with xml logic unless there is great discipline across several different skill-sets.


Support for aspects. Even if the team have a good understanding of aspects it can be very tricky to set up a maven build script for aspect compilation and then to propagate those aspects to a jboss installation for load time weaving. However together with ProtoJ's built in support for aspectj and ease of manipulation of the JAVA_OPTS environment variable from java code, the serverdemo project is able to add tracing code to a jboss class.


Ease of use. Any API is always a potential source of confusion if only because you may not have used it before. ProtoJ tries to soften the blow with just a few key design decisions that hopefully (you can be the judge) make the source code documented below easy to follow:

  1. There is only one domain object for you to create, which is StandardProject. What's more you can use its constructors rather than hunting for factories.
  2. There are no interfaces for you to implement or classes for you to extend. Indeed the ProtoJ API contains no interfaces and all classes are marked as final.
  3. The StandardProject class provides all of its functionality through other pojos, which are typically available with calls to the various StandardProject.getXXXFeature() methods.

Actually there is just one interface called ArgRunnable that you will sometimes need to implement, which is no more than a Runnable whose run method can accept a generic parameter. However this is used more as a function pointer or closure or functor in the absence of any other way of plugging in functionality in the java language.


Creating the Project

If you don't wish to create the project and are just skimming through the docs, then you can browse the source code in the subversion respository. Otherwise carry out the following steps:


Download the ProtoJ jar file. As with all the sample projects, serverdemo is created by executing the ProtoJ executable jar file:

$ java -jar protoj-exe-jdk5-1.9.2.jar -sample serverdemo

Download the jboss application server. Most sample projects contain all the resources they need, however due to their enormous sizes, the jboss 5.0.1 server for java 5 and jboss 5.0.1 server for java 6 needs to be downloaded separately. Ensure your download is placed at src/jboss-5.0.1.GA.zip or src/jboss-5.0.1.GA-jdk6 under the newly created project depending on which version you have selected. Don't worry, serverdemo will detect which jdk you are using and look for the right one to expand.


Setting Up Your Environment

There is a chance the demo will work without further intervention. However...

Although great care has been taken to ensure serverdemo runs out of the box, it's still possible that your environment imposes unforeseen memory and jboss port restraints. Fortunately these are very simple to adjust for ProtoJ projects: just edit the properties files located under conf/profile/conf and apply the changes with the dirconf command as described below. If you start seeing "port already in use" messages or OutOfMemoryError reports then you know adjustments are required.

When running the commands it's advisable to set the protoj.quiet property to true (see the dirconf command below) and follow the protoj.log file in the project log directory, which you can do on unix platforms with tail -f log/protoj.log. For windows users either google for a stand-alone tail equivalent, or better still install cygwin. Or even just keep refreshing the log file in TextPad.

Viewing the output at the console is also possible by setting the protoj.quiet property to false`, but the stream is much less frequent. That's because during a typical command, child virtual machines are forked and only give up their output to the parent virtual machine in the form of an in memory string that the parent streams on their behalf. This also increases the memory requirements for long lived child virtual machines that write a lot to the console appender.


Executing the Use-cases

When you are satisfied with your property values then you can apply the profile configuration. This will copy all the files in the conf/profile directory and overlay them onto the root directory, with all properties files merged and ${var} placeholders replaced:

$ ./serverdemo.sh "dirconf -name profile -interpolate"

In order to compile and execute the use cases, serverdemo requires the apache http component jars as specified in src/java/resources/org/serverdemo/pom.xml. This command will download them from the maven central repository into the project lib directory:

$ ./serverdemo.sh retrieve

You can now compile the source code. You can safely ignore the warning from the aspectj compiler, since it concerns advice that can't yet be applied and the advice isn't applied until runtime (loadtime) anyway.

$ ./serverdemo.sh compile

You can also review the list of available commands at any time:

$ ./serverdemo.sh help

The expand command will expand the jboss zip file appropriate for the jdk you using located in the src directory, to underneath the root and overlay global project resources located on the classpath onto it. These resources can include velocity markup in order to increase control over the jboss configuration files:

$ ./serverdemo.sh expand

The dirconf command must always be executed after the jboss archive has been expanded because there are local files that must be used to configure the jboss server with, that specify unique ports for example:

$ ./serverdemo.sh "dirconf -name profile -interpolate"

Create the serverdemo.jar file to add to jboss that contains our tracing aspect. The serverdemo lib directory is referenced by jboss due to a custom jboss-service.xml file that was overlayed during the expand command:

$ ./serverdemo.sh archive

Create a distribution of serverdemo to be executed on other machines. The result is an executable jar file called serverdemo-dist.jar that when executed will expand the project tar file it contains to the current directory:

$ ./serverdemo.sh dist

Extract the distribution to another location:

$ mkdir somedir
$ cd somedir
$ java -jar /path/to/serverdemo/target/archive/serverdemo-dist.jar

You may need to relax the permissions in the extracted project since the underlying ant implementation isn't capable of preserving them. Hopefully this will be fixed in a future version of ProtoJ. On windows you can use the explorer gui to do this, but on unix/linux try the following command:

$ chmod -R 777 somedir/serverdemo

There will be another copy of the entire serverdemo project, go to it:

$ cd somdir/serverdemo/bin

Execute the dirconf command once more - remember this is the new copy of the project so the configuration needs to be applied:

$ ./serverdemo.sh "dirconf -name profile -interpolate"

Start the jboss server. The ServerDemoProject.startServer() implementation method blocks waiting for the server to become available or until a certain amount of time has passed:

$ ./serverdemo.sh start

WINDOWS USERS: the ant exec command that launches jboss doesn't appear to respect the spawn attribute in the context it is being called in. So until a workaround is implemented you will have to ctrl-c the serverdemo. You can do this as soon as the jboss window appears. Then use the serverdemo ping command to check when the server is available.


When the command returns successfully the server will be available. Go to the jboss web page specifying the port that originated from the jboss.bindings.properties file: http://localhost:11151.


Check the serverdemo tracing aspect has worked. The JbossWatcher.aj aspect adds code to the org.serverdemo.system.JbossWatcher class that traces all its method calls into a log file:

$ cat ../jboss-5.0.1.GA/bin/serverdemo.log

Stop the jboss server. The ServerDemoProject.stopServer() implementation method blocks waiting for the server to come done or until a certain amount of time has passed:

$ ./serverdemo.sh stop

Modify the profile in preparation for debugging ServerDemoProject start command virtual machine by editing conf/profile/conf/serverdemo.properties and ensuring the following lines of code are present, although you may have to find a spare port if the one in this example is already taken:

protoj.debug.start
protoj.debug.suspend.start
protoj.debug.port.start=11121

When the profile is reapplied, calling the start command will cause the virtual machine to suspend whilst waiting for a connection from your debugger of choice. Then set a breakpoint at the top of ServerDemoProject.startServer().


Modify the profile in preparation for debugging the jboss virtual machine by editing conf/profile/conf/jboss.opts.properties and ensuring the following lines of code are present, although you may have to find a spare port if the one in this example is already taken:

serverdemo.jboss.debug
serverdemo.jboss.debug.port=11131
serverdemo.jboss.debug.suspend

When the profile is reapplied, calling the start command will cause the virtual machine to suspend whilst waiting for a connection from your debugger of choice. Then set a breakpoint at the top of the JbossWatcher before advice.

A word of advice for eclipse users: ensure the aspect is opened with the aspectj editor or you won't be able to set the breakpoint. This editor is available from the right click context menu for any aspect file.


Now that the profile has been modified then it just remains to apply it and start the server. After executing the dirconf command you can take a quick look at the conf/all.project.properties file if you like to double check that the newly added properties have been applied:

$ ./serverdemo.sh "dirconf -name profile -interpolate"
$ ./serverdemo.sh start

Then try attaching your debugger to ports 11121 and 11131 in turn, or any other ports that you may have configured and get ready for the breakpoints to be triggered. Whilst you are debugging the startServer() method, you can even see the jboss virtual machine being configured for debugging on port 11131, since this method is responsible for handling jboss properties!


Reviewing the Java Source Code

As is typical with ProtoJ projects there are two java classes that coordinate the project use-cases. The first is the bootstrapping class that for example can retrieve dependencies and "in-code compile" the remainder of the project. Clearly these abilities cannot belong to the main project class:

package org.serverdemo.core;

import protoj.lang.CompileFeature;
import protoj.lang.DependencyStore;
import protoj.lang.StandardProject;
import protoj.util.ArgRunnable;

/**
 * This is the entry point of the virtual machine that handles commands related
 * to compilation. It is launched directly from the shell script and delegates
 * other commands to the main ServerDemoProject virtual machine.
 * 
 * @author Ashley Williams
 * 
 */
public final class ServerDemoCore {
	/**
	 * Called from serverdemo.sh.
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		ServerDemoCore core = new ServerDemoCore(args);
		core.dispatchCommands();
	}

	/**
	 * See {@link #getProject()}.
	 */
	public final StandardProject project;

	/**
	 * Makes the compile use-case available and provides a link to the
	 * AlienProject class that in turn provides even more use-cases.
	 * 
	 * @param args
	 */
	public ServerDemoCore(String[] args) {
		// the central domain object is created with the command line
		// arguments and some additional version information
		project = new StandardProject(args, "1.0");

		// enables configuration support so that files from conf/profile get
		// merged to the project root tree
		project.initDirConfig();

		// alternatively change the scp credentials below and then you can use
		// the "scpconf" command to apply a profile stored at "host" in the
		// "~/allprofiles/" profile directory and use the scpconf command
		project.initScpConfig("configuser@host:~/allprofiles");

		// enables jar downloading from the maven repository to the lib dir - we
		// choose ivy here because transitive dependencies can be switched off,
		// but initMaven can be used instead
		project.getRetrieveFeature().initIvy("/org/serverdemo/ivy.xml");

		// adds httpclient as a new dependency of this project, the ivy.xml file
		// accesses the DependencyStore using velocity and builds up the xml
		// dependency entities rather than hardcoding them
		DependencyStore store = project.getDependencyStore();
		store.addRepoDependency("org.apache.httpcomponents", "httpclient",
				"4.0-beta2");
		store.addRepoDependency("org.apache.httpcomponents", "httpcore",
				"4.0-beta3");

		// project resources can access the project code with velocity markup -
		// eg the maven and ivy files don't have to use hard-coded values
		project.getResourceFeature().getContext().put("serverdemo", this);

		// load time weaving is enabled for this project
		project.getDispatchFeature().initLoadTimeWeaving(null);

		// enables compilation for the given memory, the compile source, java
		// version and any specified cross compilation options
		project.getCompileFeature().initConfig(true,
				new ArgRunnable<CompileFeature>() {
					public void run(CompileFeature feature) {
						// all parameters here can be overridden with system
						// properties defined in properties files if any
						feature.initAjcCrossCompile(null, null, null);
						feature.initAjcSource(getJavaSpecificationVersion());
						feature.initAjcMemory("16m");
					}
				});

		// provides the link to the main project class
		project.getDispatchFeature().initBootstrap(
				"org.serverdemo.system.ServerDemoProject", "8m");
	}

	/**
	 * Dispatches command line arguments to the commands that arrived in the
	 * main method of this class.
	 */
	public void dispatchCommands() {
		project.getDispatchFeature().dispatchCommands();
	}

	/**
	 * The top level protoj pojo.
	 * 
	 * @return
	 */
	public StandardProject getProject() {
		return project;
	}

	/**
	 * This is the version of java that we are running in.
	 * 
	 * @return
	 */
	public String getJavaSpecificationVersion() {
		return System.getProperty("java.specification.version");
	}

}

The second java class is the main project class that contains all the project- specific configuration and use-cases defined by the business requirements. In this example those requirements include such use-cases as starting and stopping a jboss server:

package org.serverdemo.system;

import java.io.File;
import java.io.IOException;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.log4j.Logger;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Chmod;
import org.apache.tools.ant.taskdefs.Delete;
import org.apache.tools.ant.taskdefs.Expand;
import org.apache.tools.ant.types.Environment;
import org.apache.tools.ant.types.Environment.Variable;
import org.serverdemo.core.ServerDemoCore;

import protoj.lang.ArchiveFeature;
import protoj.lang.ClassesArchive;
import protoj.lang.ProjectArchive;
import protoj.lang.ProjectLayout;
import protoj.lang.PropertyInfo;
import protoj.lang.ResourceFeature;
import protoj.lang.ScpFeature;
import protoj.lang.StandardProject;
import protoj.util.AntTarget;
import protoj.util.ArgRunnable;
import protoj.util.CommandTask;

/**
 * The class demonstrates some of the ways that a jboss server instance can be
 * controlled. For example:
 * <ul>
 * <li>downloading third party artifacts from the maven repositories</li>
 * <li>extracting and customizing the jboss application server and configuring
 * permissions</li>
 * <li>starting the instance</li>
 * <li>stopping the instance</li>
 * <li>adding remote debugging</li>
 * <li>adding performance tuning options</li>
 * <li>adding load time weaving capabilities</li>
 * <li>providing custom binding properties</li>
 * <li>replacing ${var} tokens inside config files</li>
 * <li>creating an executable jar of the entire project</li>
 * </ul>
 * <p>
 * The implementation is not heavily refactored, since I believe a blunter,
 * linear style is more suited to tutorial code.
 * 
 * @author Ashley Williams
 * 
 */
public final class ServerDemoProject {
	/**
	 * Called from ServerDemoCore.
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		new ServerDemoProject(args).dispatchCommands();
	}

	/**
	 * The underlying ProtoJ pojo.
	 */
	private StandardProject delegate;

	/**
	 * The location of the expanded jboss application server.
	 */
	private File jbossDir;

	/**
	 * The port used when debugging jboss.
	 */
	private PropertyInfo jbossDebugPort;

	/**
	 * The suspend value used when debugging jboss.
	 */
	private PropertyInfo jbossDebugSuspend;

	/**
	 * The flag used to determine whether or not to debug jboss.
	 */
	private PropertyInfo jbossDebug;

	/**
	 * The additional free-typed JAVA_OPTS configuration.
	 */
	private PropertyInfo jbossJavaOpts;

	/**
	 * The port used for jboss jnp service.
	 */
	private PropertyInfo jbossNamingPort;

	/**
	 * The ip address used by most jboss services to bind to.
	 */
	private PropertyInfo jbossBindAddress;

	/**
	 * The port used for http connections to jboss.
	 */
	private PropertyInfo jbossWebPort;

	/**
	 * The maximum amount of memory that the jboss vm may allocate.
	 */
	private PropertyInfo jbossMaxmem;

	/**
	 * The reduced functionality available in the bootstrap vm.
	 */
	private ServerDemoCore core;

	/**
	 * Configures the various features offered by ProtoJ.
	 * 
	 * @param args
	 */
	public ServerDemoProject(String[] args) {
		// contains the protoj StandardProject instance with all the standard
		// use-cases built in
		core = new ServerDemoCore(args);

		// a cached reference to the ProtoJ pojo
		delegate = core.getProject();

		// enables junit testing with the specified amount of memory
		delegate.initJunit("16m");

		ArchiveFeature archive = delegate.getArchiveFeature();

		// enables classes jar file creation with the specified jar name
		ClassesArchive classesArchive = archive.getClassesArchive();
		classesArchive.addEntry("serverdemo", null,
				"org/serverdemo/**/* META-INF/**/*", null);
		classesArchive.initClasspathLib("serverdemo");

		// enables creation of a self extracting project jar and
		// ensures the expanded jboss directory is added to it
		archive.initSelfExtractingArchive("serverdemo-dist", null,
				"serverdemo", null, null);
		archive.getProjectArchive().initConfig(
				new ArgRunnable<ProjectArchive>() {
					public void run(ProjectArchive projectArchive) {
						String includes = jbossDir.getName() + "/**";
						projectArchive.addFileSet("755", "755", includes, null);
					}
				});
		delegate.getCommandStore().addCommand("dist", "16m", new Runnable() {
			public void run() {
				delegate.getArchiveFeature().createSelfExtractingArchive(true,
						true, true);
			}
		}).initHelpString("creates a distribution of the serverdemo project");
		delegate.getCommandStore().addCommand("expand", "16m", new Runnable() {
			public void run() {
				expand();
			}
		}).initHelpString("expands jboss to the root directory");
		delegate.getCommandStore().addCommand("start", "12m", new Runnable() {
			public void run() {
				startServer();
			}
		}).initHelpString("starts the jboss default server");
		delegate.getCommandStore().addCommand("ping", "12m", new Runnable() {
			public void run() {
				pingServer(10, 10, true);
			}
		}).initHelpString("pings the jboss server to check status");
		delegate.getCommandStore().addCommand("stop", "8m", new Runnable() {
			public void run() {
				stopServer();
			}
		}).initHelpString("stops the jboss default server");

		// Experiment by making adjustments to this command. It hasn't been
		// tested and definitely won't work considering the bogus remote
		// location configured as part of the scp feature.
		delegate.getCommandStore().addCommand("mycmd", "32m", new Runnable() {
			public void run() {
				ArchiveFeature archive = delegate.getArchiveFeature();
				ScpFeature scpFeature = delegate.getScpFeature();

				// expand the untouched jboss
				expand();

				// make adjustments to jboss
				delegate.getDirconfFeature().configure("profile", true);

				// create our first party jars
				archive.getClassesArchive().createArchives();
				archive.getSourceArchive().createArchives();

				// create the executable jar of the whole project
				archive.createSelfExtractingArchive(true, true, true);

				// scp the executable jar to the release directory
				String toDir = "releasemgr@http://releasemachine:/opt/releases";
				scpFeature.copyClasses("serverdemo-dist", toDir, "");
				// see CompileCommand source for an example of how to
				// add command line options with jopt simple
			}
		}).initHelpResource("experimental command");

		jbossBindAddress = delegate.getPropertyStore().addInfo(
				"jboss.bind.address", "the jboss bind address");

		jbossNamingPort = delegate.getPropertyStore().addInfo(
				"serverdemo.jboss.naming.port", "the jboss jnp bootstrap port");

		jbossWebPort = delegate.getPropertyStore().addInfo(
				"serverdemo.jboss.web.port", "the jboss http connection port");

		jbossDebug = delegate.getPropertyStore().addInfo(
				"serverdemo.jboss.debug", Boolean.FALSE.toString(),
				Boolean.TRUE.toString(),
				"switch used to enable jboss debugging");

		jbossDebugPort = delegate.getPropertyStore().addInfo(
				"serverdemo.jboss.debug.port", "11161", "11161",
				"port used for debugging jboss");

		jbossDebugSuspend = delegate.getPropertyStore().addInfo(
				"serverdemo.jboss.debug.suspend", "n", "y",
				"whether or not to suspend when debugging jboss");

		jbossMaxmem = delegate.getPropertyStore().addInfo(
				"serverdemo.jboss.maxmemory", "512m", "",
				"the maximum amount of memory that the jboss vm may allocate");

		jbossJavaOpts = delegate.getPropertyStore().addInfo(
				"serverdemo.jboss.java.opts",
				"additional JAVA_OPTS not related to debugging or weaving");

		// when expanded this is the jboss root directory
		jbossDir = new File(delegate.getLayout().getRootDir(), "jboss-5.0.1.GA");
	}

	/**
	 * Dispatches command line arguments to the commands that arrived in the
	 * main method of this class.
	 */
	public void dispatchCommands() {
		delegate.getDispatchFeature().dispatchCommands();
	}

	/**
	 * Expands the jboss artifact as returned by {@link #getJbossZipName()}
	 * found directly under the source directory to the root directory. Since
	 * this is a large file it doesn't ship with this project and must instead
	 * be downloaded from the jboss website and place directly under the src
	 * directory.
	 * <p>
	 * During expansion all the shipped jboss server instances are deleted
	 * except for the one named "default". This instance is then modified with
	 * our custom files stored in the src/resources directory, using the ProtoJ
	 * ResourceFeature pojo.
	 * <p>
	 * Although we don't take advantage of the ability in serverdemo, ProtoJ
	 * classpath resources need not be static. They can contain velocity markup,
	 * which means they can refer to all methods and properties from this
	 * project instance.
	 * 
	 */
	public void expand() {
		ProjectLayout layout = delegate.getLayout();
		File srcDir = layout.getSrcDir();
		File jbossArtifact = new File(srcDir, getJbossZipName());

		// verifies that the jboss artifact has been downloaded
		if (!jbossArtifact.exists()) {
			StringBuilder builder = new StringBuilder();
			builder
					.append("Expected to find downloaded jboss artifact with this path:\n");
			builder.append(jbossArtifact.getAbsolutePath());
			builder
					.append("\nTry the following url that worked at the time of testing for version 5:\n");
			builder
					.append("http://sourceforge.net/project/downloading.php?group_id=22866&filename=jboss-5.0.1.GA.zip&a=50047482");
			builder.append("\nor the following url for version 6:\n");
			builder
					.append("http://sourceforge.net/project/downloading.php?group_id=22866&filename=jboss-5.0.1.GA-jdk6.zip&a=44097217");
			throw new RuntimeException(builder.toString());
		}

		AntTarget target = new AntTarget("serverdemo-project");
		target.initLogging(Project.MSG_INFO);

		// adds a task to expand the jboss zip file
		Expand expand = new Expand();
		target.addTask(expand);
		expand.setTaskName("expand");
		expand.setSrc(jbossArtifact);
		expand.setDest(layout.getRootDir());

		// adds a task to trim unwanted content
		addDelete(target, new File(jbossDir, "docs"));
		addDelete(target, new File(jbossDir, "server/all"));
		addDelete(target, new File(jbossDir, "server/minimal"));
		addDelete(target, new File(jbossDir, "server/standard"));
		addDelete(target, new File(jbossDir, "server/web"));

		// adds a task to update permsissions
		Chmod chmod = new Chmod();
		target.addTask(chmod);
		chmod.setTaskName("update-permission");
		chmod.setDir(jbossDir);
		chmod.setIncludes("**/*");
		chmod.setPerm("700");

		target.execute();

		// modifies the default server with our custom content and ensures
		// velocity markup is handled during the copy
		ResourceFeature resourceFeature = delegate.getResourceFeature();
		String prefix = "/org/serverdemo/jboss";
		resourceFeature.filterResourceToDir(prefix,
				"server/default/deploy/properties-service.xml", jbossDir);
		resourceFeature.filterResourceToDir(prefix,
				"server/default/conf/jboss-service.xml", jbossDir);
	}

	/**
	 * Returns "jboss-5.0.1.GA-jdk6" if this is a java 6 vm,
	 * "jboss-5.0.1.GA.zip" if it is version 5. There are two versions of the
	 * jboss application server and we need to know which one to expand. Helper
	 * for {@link #expand()}.
	 * 
	 * @return
	 */
	private String getJbossZipName() {
		String name;
		String version = core.getJavaSpecificationVersion();
		if (version.contains("6")) {
			name = "jboss-5.0.1.GA-jdk6.zip";
		} else if (version.contains("5")) {
			name = "jboss-5.0.1.GA.zip";
		} else {
			throw new RuntimeException("demo only executes on java 5 or java 6");
		}
		return name;
	}

	/**
	 * Adds an ant task that deletes the specfied directory. Helper for
	 * {@link #expand()}.
	 * 
	 * @param target
	 * @param dir
	 */
	private void addDelete(AntTarget target, File dir) {
		Delete delete = new Delete();
		target.addTask(delete);
		delete.setTaskName("trim-" + dir.getName());
		delete.setDir(dir);
	}

	/**
	 * Starts the default server instance. The process is spawned so that this
	 * method will continue immediately, but we will have no clue whether or not
	 * the process was successful. Therefore it subsequently makes a limited
	 * number of attempts to ping the jboss server for activity.
	 * <p>
	 * The jboss JAVA_OPTS string is also built up and applied based on any
	 * configured user properties. These are shipped in the
	 * jboss.opts.properties file, but they can go anywhere.
	 */
	public void startServer() {
		// creates a command that executes the jboss run shell script
		File jbossBinDir = new File(jbossDir, "bin");
		String runScript = new File(jbossBinDir, "run.").getCanonicalPath();
		String bindAddress = jbossBindAddress.getMandatoryValue();
		String commandLine = "-c default -b " + bindAddress;
		CommandTask task = new CommandTask(jbossBinDir, runScript, commandLine,
				"\"Serverdemo Jboss Application Server\"");
		task.initLogging(Project.MSG_INFO);
		task.initSpawn(true);

		StringBuilder allJavaOpts = new StringBuilder();

		// builds up load time weaving config which is mandatory for serverdemo
		File libDir = delegate.getLayout().getLibDir();
		File weaverJar = new File(libDir, "aspectjweaver.jar");
		allJavaOpts.append("-javaagent:" + weaverJar.getAbsolutePath());

		// builds up optional remote debug config based on user properties
		boolean debug = jbossDebug.getBooleanValue();
		if (debug) {
			String port = jbossDebugPort.getValue();
			String suspend = jbossDebugSuspend.getValue();
			String debugConfig = " -Xdebug -Xrunjdwp:transport=dt_socket,address=%s,server=y,suspend=%s";
			allJavaOpts.append(String.format(debugConfig, port, suspend));
		}

		// applies the configured jboss maximum memory setting
		allJavaOpts.append(" -Xmx" + jbossMaxmem.getMandatoryValue());

		// adds optional additional free-typed config based on user properties
		String javaOpts = jbossJavaOpts.getValue();
		if (javaOpts.length() > 0) {
			allJavaOpts.append(" ");
			allJavaOpts.append(javaOpts);
		}

		// applies the resultant JAVA_OPTS environment variable
		Variable var = new Environment.Variable();
		var.setKey("JAVA_OPTS");
		var.setValue(allJavaOpts.toString());
		task.getExecTask().addEnv(var);

		String raw = "starting server with\ncommand line options: %s\njava options: %s";
		String message = String.format(raw, commandLine, allJavaOpts);
		delegate.getLogger().info(message);

		task.execute();

		// waits for up to four minutes to check for the jboss http port
		if (!pingServer(24, 10, true)) {
			throw new RuntimeException(
					"jboss didn't start in a reasonable amount of time");
		}
		delegate.getLogger().info("server started ok");
	}

	/**
	 * Stops the jboss server if it is running. The jnp address used is
	 * calculated by coordinating with the user properties.
	 * <p>
	 * The properties are shipped in the jboss.bindings.properties file, but
	 * they can go anywhere.
	 */
	public void stopServer() {
		File jbossBinDir = new File(jbossDir, "bin");
		String runScript = new File(jbossBinDir, "shutdown.")
				.getCanonicalPath();

		// compose the address and stop the server
		String namingPort = jbossNamingPort.getMandatoryValue();
		String bindAddress = jbossBindAddress.getMandatoryValue();
		String address = "jnp://" + bindAddress + ":" + namingPort;
		delegate.getLogger().info("stopping server at " + address);
		CommandTask task = new CommandTask(jbossBinDir, runScript, "-s "
				+ address, null);
		task.initLogging(Project.MSG_INFO);
		task.initSpawn(true);
		task.execute();

		// blocks for a while to check for the jboss http port
		if (!pingServer(3, 10, false)) {
			throw new RuntimeException(
					"jboss didn't stop in a reasonable amount of time");
		}

		delegate.getLogger().info("server stopped ok");
	}

	/**
	 * Checks the health of the jboss server by pinging its default web page the
	 * specified number of times. Can be configured to check if the server has
	 * started or stopped.
	 * 
	 * @param maxAttempts
	 *            the number of pings that should be tried
	 * @param waitSeconds
	 *            how long to wait between pings
	 * @param checkStarted
	 *            true if you want to check if the server has started, false if
	 *            you want to check the server has stopped
	 * @return true if the server was seen to have started or stopped within the
	 *         allowable time, or false otherwise
	 */
	public boolean pingServer(int maxAttempts, int waitSeconds,
			boolean checkStarted) {
		boolean conditionMet = false;
		int currentAttempt = 0;
		HttpClient httpclient = new DefaultHttpClient();

		Logger logger = delegate.getLogger();
		try {
			String webPort = jbossWebPort.getMandatoryValue();
			String address = "http://localhost:" + webPort;
			logger.info(String.format("pinging %s %s times", address,
					maxAttempts));
			HttpGet httpget = new HttpGet(address);

			// this response handler merely captures the status code
			ResponseHandler<Integer> responseHandler = new ResponseHandler<Integer>() {
				public Integer handleResponse(final HttpResponse response)
						throws HttpResponseException, IOException {
					int statusCode = response.getStatusLine().getStatusCode();
					return statusCode;
				}
			};

			// pings until the correct response code is returned
			// and while there are still attempts remaining
			while (!conditionMet && currentAttempt < maxAttempts) {
				currentAttempt++;
				Thread.sleep(waitSeconds * 1000L);
				try {
					Integer statusCode = httpclient.execute(httpget,
							responseHandler);
					logger.info("got status " + statusCode);
					conditionMet = statusCode.equals(200) && checkStarted;
				} catch (Exception e) {
					logger.info("address not available");
					// if we are checking for a stopped server then success
					// otherwise we must carry on checking
					conditionMet = !checkStarted;
				}
			}
		} finally {
			httpclient.getConnectionManager().shutdown();
		}

		return conditionMet;
	}

}

Reviewing the Aspectj Source Code

One of the advantages of ProtoJ is that aspectj files aren't treated as second class citizens that need to be tucked away in a separate diretory called aspects or such like: they are simply placed alongside their java brethren in the appropriate package. You may have noticed there are no checked exceptions referenced in any of the java source code and the reason for this is the presence of the following aspect that effectively turns them all into runtime exceptions:

package org.serverdemo.system;

/**
 * Exceptions are softened in methods that are called from this project.
 * 
 * @author Ashley Williams
 * 
 */
public final aspect SoftException {
	declare soft : Exception : execution(* org.serverdemo..*.*(..)) && within(org.serverdemo..*);
	declare soft : Exception : execution(org.serverdemo..*.new(..)) && within(org.serverdemo..*);
}

This aspect is confined only to the serverdemo source code and isn't woven into any of the jboss libraries for example.


On the other hand the jboss watcher aspect is designed explicitly to be woven into the jboss codebase. It does nothing more than insert logging code into an arbitrary jboss class so that its lifecycle can be monitored. The logger is configured in-code rather than using a log4j.xml file for neater encapsulation and merely writes to a serverdemo.log file in the jboss bin directory:

package org.serverdemo.system;

import java.io.File;

import org.apache.log4j.Appender;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.RollingFileAppender;

/**
 * Simple demonstration aspect that merely logs calls
 * to methods on some jboss instance.
 * 
 * @author Ashley Williams
 *
 */
public final aspect JbossWatcher {
	private Logger logger;
	
	/**
	 * Adds a log call before the execution of Ejb3Deployer methods.
	 */
	before() : execution(* org.jboss.ejb3.deployers.Ejb3Deployer.*(..)) {
		String signature = thisJoinPoint.getSignature().toShortString();
		logger.info("serverdemo has detected a call to " + signature);
	}
	
	/**
	 * The logger is created with a single file appender that is configured
	 * to write output to the jboss bin directory.
	 */
	public JbossWatcher() {
		// we are running from the jboss bin directory so will have to
		// navigate to project log directory
		File logFile = new File(".", "serverdemo.log");
		String path = logFile.getAbsolutePath();
		logger = Logger.getLogger("serverdemo");
		String name = "serverdemo.fileAppender";

		String pattern = "%d{HH:mm:ss,SSS} [%t] %m%n";
		PatternLayout layout = new PatternLayout(pattern);
		Appender appender = new RollingFileAppender(layout, path);
		appender.setName(name);
		logger.addAppender(appender);
	}
}

When an aspect is to be woven into another class, aspectj demands that an aop xml file should be provided. For completeness here is the aop file that causes the jboss watcher aspect to be woven into the jboss class:

<aspectj>
	<aspects>
		<aspect name="org.serverdemo.system.JbossWatcher" />
		<include within="org.serverdemo..*" />
	</aspects>
	<weaver>
		<include within="org.jboss.ejb3.deployers.Ejb3Deployer" />
	</weaver>
</aspectj>

Reviewing the Properties Files

Properties have been split across several files in the profile/conf directory. The first of these is jboss.opts.properties that contains just the properties used in configuring the JAVA_OPTS environment variable. This is the hook that jboss provides for configuring the jboss virtual machine:

# Contains all properties related to the JAVA_OPTS environment variable.
# The debugging and aspect weaving configuration have been broken out
# into related properties as the final string is fairly standard. The
# same can be done with some of the performance tuning information if
# there is a particular way of approaching this on your project.
#
# Author: Ashley Williams

# uncomment to enable debugging
#serverdemo.jboss.debug

# when debugging is enabled this port is used
serverdemo.jboss.debug.port=11131

# toggles vm suspending when debugging is enabled
serverdemo.jboss.debug.suspend

# maximum jboss memory - when commented out a default value is used by the app
#serverdemo.jboss.maxmem

# other JAVA_OPTS settings free typed
#serverdemo.jboss.java.opts=-XX:MaxPermSize=256m -Dorg.jboss.resolver.warning=true -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000

Properties used for for customizing the jboss configuration files have been split off into a file called jboss.bindings.properties that contains unique port numbers for example:

# Configuration for the jboss service binding manager which is responsible
# for most of jboss config
#
# Author: Ashley Williams

# ip addresses bound to most jboss services
jboss.bind.address=0.0.0.0

# the value for the tomcat port used in server.xml
serverdemo.jboss.web.port=11151

# the jnp port
serverdemo.jboss.naming.port=1099

There is no guarantee that the memory requirements of the jboss demonstration project itself are the same from platform to platform and so some generous defaults are provided in the serverdemo.properties file. ProtoJ employs an aspect called UserOverride in order to intercept values specified in code with those specified as properties:

# Contains all properties related to the various serverdemo
# virtual machines, not including the jboss virtual machine.
# See app.properties for more information
#
# Author: Ashley Williams

# Memory values specified here will override any values hardcoded in the source.
protoj.compile.memory=32m
protoj.main.memory=32m
protoj.memory.retrieve=32m
protoj.memory.compile=32m
protoj.memory.expand=32m
protoj.memory.archive=32m
protoj.memory.dist=32m
protoj.memory.start=32m
protoj.memory.stop=32m
protoj.memory.ping=32m

# no console appender enabled
protoj.quiet=true

# uncomment to enable "start command" debugging
#protoj.debug.start

# toggles vm suspending when "start command" debugging is enabled
protoj.debug.suspend.start

# when "start command" debugging is enabled this port is used
protoj.debug.port.start=11121

And just for completeness much more information about where the properties in the serverdemo.properties file originate are contained in the app.properties file:

# Author: Ashley Williams

# PROPERTIES OVERVIEW
# ===================

# A ProtoJ project will read in properties from any properties file in the conf
# directory so feel free to split them as you see fit. Of course you may still
# have to respect properties file names expected by third party libraries.
#
# Although you can work with properties files in the application conf directory
# a better option is to modify them in a profile directory instead. A  profile
# directory is a mini snapshot of the entire application directory tree that
# contains overrides of any project file with a matching path. There are
# several advantages to this:
#
#    1. A properties file from the profile directory gets its contents
#       merged with the matching application properties file if it exists. Any
#       other type of file will simply replace any matching project file.
#
#    2. Any ${var} tokens in the files contained in the profile directory will be
#       optionally replaced with their interpolated values. It should be noted
#       that using this feature will cause all conf properties to be condensed
#       into a single "all.project.properties" file. To enable this behavour,
#       "StandardProject.initConfig(true)" must be called from the project API.
#
# The latter token replacement behavour is useful in situations where a third
# party library only supports properties files where all ${var} tokens have been
# fully resolved. Once you have finished modifying your profile directory then
# you can apply the changes by executing the "config" command with no options.
#
# A profile directory can exist on any mounted share, just specify the location
# with the ProtoJ dirconfig API when configuring the project. Alternatively a
# profile directory can reside anywhere on the network and be located via scp,
# just specify the location with the ProtoJ scpconf API when configuring the
# project.

# AVAILABLE PROPERTIES
# ====================

# There are a number of properties available out of the box with ProtoJ. Take a
# look through the selection below and either modify or place in properties files
# of your own choosing. If you start seeing OutOfMemoryErrors when executing a
# project use-case, then don't be alarmed - just assign a more generous maximum
# memory setting for the problematic vm that works for you particular environment.
# There are 3 virtual machines involved for each command as follows with
# suggested initial values for unix and windows platforms:
#
# 	script ---> bootstrap-vm ---> main-vm ---> command vm
# 	            ~1k               ~1k          ~16m        (unix)
# 	            ~16m              ~4m          ~16m        (windows)
#
# The main vm is responsible for dispatching the individual commands Each command
# then executes in its own vm. A small bootstrap vm is launched directly from the
# shell script in order to reach the main vm in the first place, so you can only
# adjust the bootstrap vm memory by editing that launch script.

# --------------------------------------------------------------------------------

# The main virtual machine is responsible for dispatching the individual commands.
# You can assign the maximum amount of memory here with the "protoj.main.memory"
# property. Additionally you can enable a remote debugging session by declaring
# the "protoj.main.debug" property, which will also cause the related debug
# properties to come into effect. 

#protoj.main.memory=32m
#protoj.main.debug
#protoj.main.debug.suspend
#protoj.main.debug.port=11111

# --------------------------------------------------------------------------------

# Each command launched from the main vm also executes in its own vm and so you
# can configure its properties in the same way. Simply substitute the name of the
# command as you would specify from the command line for the "xxx" place-holders
# in the template below and repeat for as many commands as you like. For example
# to set the maximum memory for the tar command specify "protoj.memory.tar".

#protoj.memory.xxx=32m
#protoj.debug.xxx
#protoj.debug.suspend.xxx
#protoj.debug.port.xxx=11111

# --------------------------------------------------------------------------------

# The junit tests are actually forked into a new vm quite separate from the junit
# command vm that launches them. This forked vm can be configured in exactly the
# same way as the previous examples.

#protoj.junit.memory=32m
#protoj.junit.debug
#protoj.junit.debug.suspend
#protoj.junit.debug.port=11111

# --------------------------------------------------------------------------------

# Cross compilation is supported with the three options recommended by the javac
# tool documentation. The properties also apply to aspectj. These overrides will
# only be applied if a CompileFeature.init*CrossCompile method is called.

#protoj.compile.target
#protoj.compile.extdirs
#protoj.compile.bootstrap

# --------------------------------------------------------------------------------

# These other compiler overrides will only be applied if one of the methods
# CompileFeature.init*Source() or Compile.init*Memory() is called respectively.

#protoj.compile.source
#protoj.compile.memory

# --------------------------------------------------------------------------------

# Enabling this property will cause a highly visible window to appear at the end
# of a command that indicates success or failure - useful if you want to monitor
# the build from across the room.

#protoj.statusWindow

# --------------------------------------------------------------------------------

# Assign false to see project output at command line, although setting to true and
# tailing the protoj.log file is the more responsive mechanism 

#protoj.quiet=false

# --------------------------------------------------------------------------------

# Assign a logging level from one of the org.apache.log4j.Level.XXX static strings

#protoj.level=INFO

# --------------------------------------------------------------------------------

# When using profiles you can override the API in order to specify the location of
# the desired profile. The "protoj.profile.home" is used to contain profiles
# mounted on the filing system whereas "protoj.profile.url" is used to contain
# profiles mounted remotely.

#protoj.profile.home=~/.acme/profiles
#protoj.profile.url=configuser@host:~/acme/profiles

Reviewing the Dependency File

ProtoJ can hook into maven and ivy in order to automatically download dependencies into the project lib directory, however the API enables you to do more than that. Take a look at the ivy.xml file used in serverdemo:

<ivy-module version="2.0">
<!-- 
This file contains velocity markup, which allows you to access java objects in
the same way as jsp. For example the list of dependencies are iterated over from
the DependencyStore instance and used to build up the xml dependency entities.

The following objects are available:

project    - the instance of StandardProject, always available by default
serverdemo - the instance of ServerDemoCore as configured by the API in that class 
 -->
	<info organisation="org.serverdemo" module="serverdemo" revision="1.0" />
	<dependencies>
#foreach($dependency in ${project.dependencyStore.repoDependencies})
        <dependency org="${dependency.groupId}" name="${dependency.artifactId}" rev="${dependency.version}" transitive="false"  />
#end
	</dependencies>
</ivy-module>

In case you didn't know, the unusual syntax is velocity markup which enables the xml file, or any other type of file on the classpath, to access the in-code objects. In this case the DependencyStore is asked for this list of dependencies so that the xml dependency entities can be dynamically created.

In other words even the smallest detail such as artifact dependencies can be coordinated across the project.


Reviewing the Shell Scripts

As mentioned in the overview, serverdemo is launched with a traditional OS specific shell script that is responsible for compiling and launching the ServerCore virtual machine. Take a look at the bash shell script and windows batch file:

--serverdemo.sh--

#!/bin/bash

ROOT_DIR=`pwd`/..
SCRIPT_NAME=`basename "$0"`

echo for the fastest output response please monitor the log/protoj.log file rather than the console 

java -Xmx1k -jar $ROOT_DIR/lib/protoj*.jar \
	"init -rootDir $ROOT_DIR -scriptName $SCRIPT_NAME" \
	"javac -body -opt compileTask.source=1.5 -opt compileTask.memoryMaximumSize=8m -opt dirSet.includes=org/serverdemo/core" \
	"java -body -opt javaTask.classname=org.serverdemo.core.ServerDemoCore -opt javaTask.maxmemory=1k" \
	"$@"
--serverdemo.bat--

@echo off

set ROOT_DIR=%~dp0..
set SCRIPT_NAME=%~nx0

echo for the fastest output response please monitor the log/protoj.log file rather than the console 

java -jar %ROOT_DIR%/lib/protoj*.jar ^
	"init -rootDir %ROOT_DIR% -scriptName %SCRIPT_NAME%" ^
	"javac -body -opt compileTask.source=1.5 -opt compileTask.memoryMaximumSize=8m -opt dirSet.includes=org/serverdemo/core" ^
	"java -body -opt javaTask.classname=org.serverdemo.core.ServerDemoCore -opt javaTask.maxmemory=1k" ^
	%*

You will notice that they are strikingly similar and consist of just a single call to the java executable. This is only possible because the snippets of script such as compileTask.memoryMaximumSize=8m are passed through to the ant API by ProtoJ. In other words the ant Javac and Java tasks have been exposed directly as beans, which lessens the need for OS specific script code and also eliminates the need for xml.


Sign in to add a comment
Hosted by Google Code