The Launcher Project


The Launcher Project

The Launcher project in the Jakarta Commons is not a strategy used to instantiate another class within a component or subsystem. Rather, it allows you to launch another Java component or subsystem as an application. As we've progressed in this chapter, each project description's factory has become a bit more abstract. In the beginning of the chapter, we illustrated the factory that created a class within the same application. The Discovery package was capable of loading a class into an application. The Launcher package is not a factory in the strictest sense of the word. However, it's a way to launch another Java class using a specific set of environment constraints. The Launcher package is great when you're creating server applications that want to launch Java applications executing in different contexts. An example of such an application type would be a Web Server with multiple domains. With the Launcher package, you can create the main Web Server processor, which manages a number of child processes responsible for each virtual domain.

The simplest case of using the Launcher classes is not that simple, even though using the Launcher class is very simple. An example of its simplicity from the user point of view is shown in Listing 3.29.

Listing 3.29
start example
 Launcher launch = new Launcher(); String args[] = new String[ 3]; args[ 0] = "-launchfile"; args[ 1] =  "/home/cgross/mydocs/book/src/common/launcher.xml"; args[ 2] = "echo"; launch.start( args); 
end example
 

In Listing 3.29, the main class being used is the class Launcher , which has one main method, start , which is used to instantiate the other class. What is puzzling is that instead of starting a class directly, the arguments defined in Listing 3.29 reference some type of launch file and another identifier, echo . At first impression , the identifier echo would appear to be a class identifier. The reality, though, is that the identifier references something else entirely.

Technical Details for the Launcher Package

The Launcher package, with its various classes, presents a problem: it's very hard to figure out what it does. Consider the description of the package described in the proposal document: " The Launcher component is designed to be a cross-platform Java application launcher. "

This description tells you nothing of the classes that are defined or implemented. As a developer, you are left entirely in the dark. Add to that the fact that you have a bin directory with a bunch of files that somebody could click on; however, the clicks don't tell you much. Therefore, it's very hard to conceptualize the Launcher package unless you identify the various classes. Tables 3.5 and 3.6 do just that.

Table 3.5: Repository details for the Launcher factory.

Item

Details

CVS repository

jakarta-commons

Directory within repository

launcher

Main packages used

org.apache.commons.lang.launcher

Table 3.6: Package and class details (legend: [launcher] = org.apache.commons.launcher).

Class/Interface

Details

[launcher].Launcher

A main class used by the caller to instantiate another class

[lang].*

Classes used by the infrastructure to instantiate the Java class (needed by Ant)

[lang].types.*

Classes used by the infrastructure to instantiate the Java class (needed by Ant)

Details of Building the Launcher Package

Building the Launcher package from the CVS repository is not complicated. You need to use Ant and ensure that the Ant jar files are in the class path ; otherwise , you'll get some compilation errors. When you compile, use the command ant dist because that will generate a directory dist within the Launcher directory. The dist directory contains a number of subdirectories, including the bin and lib directories. The bin directory is the most important because it contains the commons-launcher.jar file, which is the compiled Launcher package. The other files are important and will be explained later in the chapter.

Architectural Details

The Launcher package depends on the Ant jar files. The Launcher package is a very clever package because it provides the programmer with a way of instantiating a class using a specific set of environment variables. This is achieved through the infrastructure of the Ant toolkit, which is a way of compiling a set of Java files. Lately, however, Ant has become an execution infrastructure for Java and other tools. When a developer clicks on a typical operating system executable, the executable will run, but the environment will be based on whatever the defaults are. Using Ant, you can define the classpaths, environment variables , and how many applications to start. Ant has become the ideal bootstrap environment. The default Ant execution environment is very extensive ; for application bootstrapping, it is a bit of overkill. The Launcher is a stripped-down version of Ant that allows only a number of Ant constructs, such as task and variable definitions. If you summarize Launcher , you get aversion of Ant that makes it simpler to launch Java tasks. Listing 3.30 shows the Ant tasks and variables that are defined. (Note that the class definition has been shortened so that it's easier to understand.) We will discuss Ant in more detail in Chapter 11.

Listing 3.30
start example
 public class Launcher implements Runnable { /**  * List of supported Ant tasks.  */ public final static Object[] SUPPORTED_ANT_TASKS = new Object[] { LaunchTask.TASK_NAME, LaunchTask.class, "ant", Ant.class, "antcall", CallTarget.class, "available", Available.class, "condition", ConditionTask.class, "fail", Exit.class, "property", Property.class }; /**  * List of supported Ant types.  */ public final static Object[] SUPPORTED_ANT_TYPES =  new Object[] { ArgumentSet.TYPE_NAME, ArgumentSet.class, JVMArgumentSet.TYPE_NAME, JVMArgumentSet.class, SysPropertySet.TYPE_NAME, SysPropertySet.class, "description", Description.class, "fileset", FileSet.class, "filelist", FileList.class, "path", Path.class, "patternset", PatternSet.class }; } 
end example
 

In Listing 3.30, the supported Ant types and tasks are a combination of already existing tasks and types, and newly defined tasks and types. The already defined tasks and types are the string-defined tasks and types, which are defined in the Ant jar file. The other definitions are specified in the Launcher package. The class Launcher is a very simple class. You could copy it and make it part of another package if you need to expand the range of supported tasks and types.

Defining a Launch File

The defined tasks and types are used in a launch file, which was specified in Listing 3.29 as launcher.xml . In Listing 3.29, the launcher.xml file is basically identical to an Ant build.xml file. This means that the launch file has a project descriptor, some definitions, and some targets. Listing 3.29 contained the identifier echo , which is a launch file target. This means that when the Launcher parses the launch file, the XML tag target will be searched. One of the found target XML tags will contain an attribute name with the value echo . The launcher.xml file used in Listing 3.29 is shown in Listing 3.31.

Listing 3.31
start example
 <project name="Ant Launcher" default="ant" basedir="."> <property environment="env"/> <property file="build.properties"/> <target name="echo"> <launch classname="com.devspace.jseng.create.ClassToLaunch" classpath="${standard-jars}:${project- src}/classes"> </launch> </target> </project> 
end example
 

In Listing 3.31, the target echo contains a single task, launch . This task has two XML attributes, which specify the classpath and classname . The attribute classpath identifies the Java classpath of the application that is about to be executed. The attribute classname identifies the Java class that will be launched. In this case, the Java class that will be launched as shown in Listing 3.32.

Listing 3.32
start example
 package com.devspace.jseng.create; public class ClassToLaunch { public static void main( String [] args) { System.out.println( "oooweee"); } } 
end example
 

The class ClassToLaunch in Listing 3.32 is a very simple class and does not contain any interesting functionality other than supporting the method main .

Launching a Java Class

When you launch a Java class file, the biggest problem is defining the class path. The basic solution has been to define the CLASSPATH environment variable with all of the appropriate jar files. However, that approach has proven to be extremely tedious since literally millions of jar files are available on the Internet these days. The launcher file massively improves this situation because you can define variables as properties, which reference individual jar files. Let's go back and consider Listing 3.31 again, and focus in on the XML tag property as illustrated in Listing 3.33.

Listing 3.33
start example
 <property environment="env"/> <property file="build.properties"/> 
end example
 

The XML tag property in Listing 3.33 defines a set of static variables that can be later referenced. The first property definition of Listing 3.33 is used to define the environment variables. This is the case because the XML tag property has an attribute environment. Ant will translate this definition as saying, " Please expose the environment variables predicated by the value env." The second property definition has a file attribute that indicates you should import the file build.properties . The variables defined within the imported file should be considered static variables. A sample build.properties is shown in Listing 3.34.

Listing 3.34
start example
 clib=${env.CATALINA_HOME}/common/lib project-root=/home/cgross/mydocs/book project-src=${project-root}/src commons-jars=:${clib}/commons-logging.jar:${clib}/commons-logging-api.jar src=. build=${project-root}/classes debug=on 
end example
 

Each line in a properties definition file like Listing 3.34 has the [identifier]=[value] pattern. The identifier defines a static variable like the tag property in the launcher file. The identifiers can then be referenced using the notation ${variable}, as we saw in Listings 3.31 and 3.34. The notation is a sequence of characters that tells the Ant parser to consider the identifier within the curly brackets as a variable to be substituted by its associated value. For example, in Listing 3.34, the variable project-root is defined. One line later, that definition is used to define the variable project-src . The value of the variable project-src would be the concatenation of variable project-root and /src , which would result in the value of /home/cgross/mydocs/book/src .

One twist in Listing 3.34 is the variable reference env.CATALINA_HOME . In the launcher file, there was a property reference to the environment variables being the value env . Therefore, a variable that starts with env has a period as a separator to the identifier, which is an environment variable. In Listing 3.34, that environment variable is CATALINA_HOME . Using a dot to separate two identifiers is very similar notation to that used in an object-oriented system. Don't let this confuse you, however, because referencing the environment variables like this is a special case. Otherwise, a dot is part of the identifier, much like an underscore can be used to separate two identifiers.

The strategy to take when developing your own launcher file is to abstract definitions such as the class path to the build.properties file. Then, within the launcher file, reference the class path using either one or a few class path variables as you saw in Listing 3.31. This approach allows an administrator to change jar files without having to update the launcher file or the Java executable.

Launching a Class File Depending on the Context

When you're building an application launcher, you also need to make specific decisions based on the conditions. For example, one very problematic issue is the ability to execute specific files dependent on the operating system, or dependent on whether the component should be debugged . In either of these situations, the decision on what to do and how to do it depends on when the launcher file is executed. In Listing 3.35, the simplest condition, which is whether the operating system is Microsoft Windows or not, is tested .

Listing 3.35
start example
 <target name="testcondition"> <condition property="classtoexecute" value="com.devspace.jseng.create.window.ClassToLaunch"> <os family="windows" /> </condition> <condition property="classtoexecute" value="com.devspace.jseng.create.unix.ClassToLaunch"> <not> <os family="windows" /> </not> </condition> </target> <target name="echo" depends="testcondition"> <launch classname="${classtoexecute}" classpath="${standard-jars}:${project-src}/classes"> </launch> </target> 
end example
 

In Listing 3.35, there are two targets. The target echo still exists, but it has an additional attribute depends . The purpose of the depends attribute is to execute another target before the target in question is executed. In Listing 3.35, the depends attribute identifies the testcondition target, which is a target with two contained condition XML tags. The condition XML tags are tasks that execute based on the contained child tags. This way of processing a condition is counter to how a programming language processes one. In a programming language like Java, the condition result executes the instructions within the condition block. In Ant, the condition result is the XML tag, and the condition tests are the instructions within the condition block. Let's look back to Listing 3.35. Here, the condition test is the XML tag os and the result is the attributes property and value . The condition is saying that if the child tags allow, please define the property classtoexecute with a value of com.devspace. In the first condition, the child tag was an operating system descriptor os , which returns a true or false value. In the second condition tag, the child tag os is embedded within a not child tag, meaning that the os result value should be inverted. These conditions then define a class to be executed dependent on the operating system.

Launch Arguments

The Launcher is based on the concept of calling Ant, which calls the Java class file. You can define the class to be launched as well as the Java classpath. The launch task has many more options other than classname and classpath. The following list defines them all and what they do:

  • debug (true false) This is a flag to start the child JVM in the Java Debugger (JDB) debugger. If you want to use Java Platform Debugger Architecture (JPDA) debugging, please refer to the bin directory of the Launcher package. In the bin directory is a launcher file example on how to start the launched process to be debugged using JPDA. The default is false .

  • displayMinimizedWindow (true false) : If set, this displays the window as minimized.

  • disposeMinimizedWindow (true false) : This is a flag specific to the Windows platform that causes the window to be discarded once the child process is completed.

  • failonerror (true false) : If this is set and the child process fails because of an exit, the calling process will fail and exit as well.

  • filterclassname : This specifies the file that implements a filter. A filter is discussed later in this chapter.

  • filterclasspath : This specifies the Java classpath used to load the filter class.

  • minimizedWindowIcon : This is an attribute specific to the Windows platform. It represents a filename that contains an icon that is displayed when the child process window is minimized.

  • minimizedWindowTitle : This is an attribute specific to the Windows platform that sets the name of the process in the taskbar when the window is minimized.

  • output : This is the name of a file that will contain the output of the child process.

  • print (true false) : This outputs the command used to start the child JVM to the standard output stream. The default is false .

  • redirectoutput (true false) : When this flag is set to true, the output of the child process is redirected to the file specified by the output attribute. The default is false .

  • requiretools (true false) : If you set this flag when executing the process, it will cause the tools.jar to be appended to the classpath. The major side effect of this is that the child JVM process must be executed using a JDK and not JRE (Java runtime). The default is false .

  • useargs (true false) : In Listing 3.29, there were a number of arguments created manually. These arguments are passed as arguments to the child process. We will discuss this further later in this chapter. The default is false .

  • usesystem (true false) : This is a flag specific for some UNIX platforms to not read the standard input stream. The default is true , which means read the standard input stream.

  • waitforchild (true false) : This causes the calling process to wait for the child process to finish before continuing any further processing. The default is false .

Passing in Command Line Arguments

When the child process is executed, it is possible to specify some command line arguments. The command line arguments can be specified in two different locations. The nature of the command line argument determines where the command line argument can be optimally stored. If the command line argument is static and does not change, then the best place to define it would be in the launcher file, as shown in Listing 3.36.

Listing 3.36
start example
 <launch classname="${classtoexecute}" classpath="${standard-jars}:${project-src}/classes"> <argset> <arg value="hello" /> </argset> </launch> 
end example
 

In Listing 3.36, the XML tag argset defines a placeholder for what could be a single or multiple command line arguments. In Listing 3.36, there is a single argument as specified by the XML tag arg. The value of the argument is specified by the attribute value, which in this case is "hello." Modifying Listing 3.32 to output the command line arguments results in Listing 3.37.

Listing 3.37
start example
 package com.devspace.jseng.create; public class ClassToLaunch { public static void main( String [] args) { if( args.length > 0) { for( int c1 = 0; c1 < args.length; c1 ++) { System.out.println( "Arg: " + args[ c1]); } } System.out.println( "oooweee"); } } 
end example
 

Running the launcher file would generate the one argument as output. The problem with using the launcher file is that the command line argument is static and cannot be changed dynamically. To generate a dynamic command line argument, you need to generate the command line argument in the client code, as shown in Listing 3.38.

Listing 3.38
start example
 Launcher launch = new Launcher(); String args[] = new String[ 4]; args[ 0] = "-launchfile"; args[ 1] =  "/home/cgross/mydocs/book/src/common/launcher.xml"; args[ 2] = "echo"; args[ 3] = "command line argument"; launch.start( args); 
end example
 

When using the method launch.start as shown in Listing 3.38, it is very important to use the correct command line arguments. The args[ 0] element references the string -launchfile , which is a command line argument consumed by the class Launcher . This command line argument specifies that the launcher file will be located at a specific location. Otherwise, the Launcher class will assume that the file launcher.xml exists in the same directory where the commons-launcher.jar file is located. The args[ 1] element references the location of the launcher.xml file, which does not need to be called launcher.xml since a specific launch file is defined. The args[ 2] element defines the target that will be executed in the launch file. Any argument after the target, such as args[ 3], can be a command line argument that is passed to the client process.

The command line argument, by default, is not passed to the client process. The launch task in the launcher file has to have the useargs attribute set to true, as shown in Listing 3.39.

Listing 3.39
start example
 <launch classname="${classtoexecute}" classpath="${standard-jars}:${project-src}/classes" useargs="true"> <argset> <arg value="hello" /> </argset> </launch> 
end example
 

When the client process executes, a command line argument will be displayed. However, Listing 3.39 will create an array of two arguments. The question is which arguments are placed at the beginning of the array and which arguments at the end. The static arguments are at the front of the argument array, and the dynamic arguments are placed afterwards. However, this is a practical observation and not a common rule. Therefore, when defining your own arguments, use key value combinations such as -argument value .

In Listing 3.38, the method launch.start is used to start a child process. You can also use the method launch.stop to stop the child process. If the caller wants to kill the child process, the method launch.killChildProcesses is used. The methods launch. isStarted and launch.isStopped make it possible for the caller to query for the status of the child process. Additionally, the class Launcher has methods to query the classpaths and other attributes of the client task.

Specifying Java VM Arguments

The arguments defined thus far are command line arguments. It is possible to specify Java VM arguments that control how the client process JVM is started. The way to specify those arguments is to use the XML tags jvmargset and jvmarg , as shown in Listing 3.40.

Listing 3.40
start example
 <launch classname="${classtoexecute}" classpath="${standard-jars}:${project-src}/classes"> <jvmargset> <jvmarg value="-verbose" /> </jvmargset> </launch> 
end example
 

In Listing 3.40, the XML tag jvmarg has a value of -verbose , which means that when the client process JVM starts, the JVM should be verbose and display plenty of messages. The purpose of being able to specify JVM arguments is to be able to debug the client process, as shown in Listing 3.41.

Listing 3.41
start example
 <jvmargset id="base.jvm.args"> <jvmarg value="-sourcepath" if="jdb"/> <jvmarg path="${app.home}/../src/java" if="jdb"/> <jvmarg value="-Xdebug" if="jpda.settings"/> <jvmarg value="-Xrunjdwp:${jpda.settings}" if="jpda.settings"/> </jvmargset> 
end example
 

In Listing 3.41, the jvmarg tags define a series of JVM instructions that will start the JPDA debugger. Contrasting Listing 3.41 with Listing 3.40, the jvmarg tags have an additional if attribute. The if attribute is a conditional for the jvmarg . The attribute specifies that if jdb or jpda.settings are defined, then the jvmarg is defined.

Using Args

In addition to command line arguments, you can define system properties. System properties are different from command line arguments in that system properties are key value combinations. The key value combinations are specified in the launcher file, as shown by Listing 3.42.

Listing 3.42
start example
 <launch classname="${classtoexecute}" classpath="${standard-jars}:${project-src}/classes"> <syspropertyset> <sysproperty key="val" value="another"/> </syspropertyset> </launch> 
end example
 

The key value combination is specified by the XML tags syspropertyset and sysproperty, as illustrated in Listing 3.42. These XML tags are not unique and could have been specified as a jvmarg using the notation > "-Dval=another" . However, the XML tag sysproperty is a cleaner approach. To be able to read the system property, the method call System.getProperty is made. The method getProperty retrieves the key based on the input parameter, which is the key to search for. If we relate the method call to Listing 3.42, the key would be val , and the method getProperty would return the value another . Using the syspropertyset approach is tidier than having to specify the properties using a jvmarg tag.

Filtering the Command

Just as a child process is about to be started, you can change all of the things that you defined using the client code or launcher file. The way to do this is to specify a filter class. The purpose of the filter class is to give the client application one last chance to modify the startup characteristics before the child process starts. A sample implementation is shown in Listing 3.43.

Listing 3.43
start example
 package com.devspace.jseng.create; import org.apache.commons.launcher.LaunchFilter; import org.apache.commons.launcher.LaunchCommand; public class LocalLauncherFilter implements LaunchFilter { public void filter(LaunchCommand launchCommand) { launchCommand.setPrint( true); } } 
end example
 

To create a filter, the class has to implement the interface LaunchFilter , as shown in Listing 3.43. The interface LaunchFilter has only the method filter to be implemented. All of the child process startup characteristics are stored in the class LaunchCommand . Going through this class definition would be redundant because the individual operations mirror the individual attributes and arguments that have been specified thus far. For example, it is possible to manually set the launch attribute print using the method setPrint and getPrint . You should use a filter only when you need to do very specific manual tweaking that cannot be coded in another technique.

To activate the filter, the launch task has an additional attribute, filterclassname , as shown in Listing 3.44.

Listing 3.44
start example
 <launch classname="${classtoexecute}" classpath="${standard-jars}:${project-src}/classes" filterclassname= "com.devspace.jseng.create.LocalLauncherFilter"> </launch> 
end example
 

If the filterclass specified is not on the classpath, then you can use the filterclasspath attribute to specify an additional classpath that contains the filter class.




Applied Software Engineering Using Apache Jakarta Commons
Applied Software Engineering Using Apache Jakarta Commons (Charles River Media Computer Engineering)
ISBN: 1584502460
EAN: 2147483647
Year: 2002
Pages: 109

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net