Tomcat Launcher: Sample Application using the AIR 2.0 Native Process API

I have been playing with the new Native Process API of AIR 2.0, and here is a first sample application I wrote. “Tomcat Launcher” is like a mini Tomcat Console that allows you to start / stop Tomcat and view the standard output log without opening a DOS box or Terminal window. It also allows you to open that log in your default Text Editor which is another new feature of AIR 2.0 that I already explored in my “Open in Excel” sample application. Tomcat Launcher also provides a generic example showing how to use the AIR 2.0 native process API to execute Java code.

Installation Instructions

  1. Download the AIR 2.0 beta runtime here.
  2. Because Tomcat Launcher uses the Native Process API, I had to create native installers:

After starting the application, enter the paths to your Java home folder and to the Tomcat installation you want to start, then click the Start button. The application will remember the folders you entered the next time you use the application. If you get a BindingException, make sure Tomcat isn’t already started. You can also click the Stop button to stop a running instance.

You can download the project here: TomcatLauncher.fxp.zip. If you just want to take a quick peak at the code, here is TomcatLauncher.mxml:

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
					   xmlns:s="library://ns.adobe.com/flex/spark"
					   xmlns:mx="library://ns.adobe.com/flex/halo"
					   title="Tomcat Launcher"
					   height="700" width="500"
					   applicationComplete="init()">
	<s:layout>
		<s:VerticalLayout paddingLeft="8" paddingRight="8" paddingBottom="8"/>
	</s:layout>

	<fx:Script>
		<![CDATA[
			import mx.controls.Alert;

			private var startTomcatProcess:NativeProcess;
			private var stopTomcatProcess:NativeProcess;

			public function init():void
			{
				if (!NativeProcess.isSupported)
				{
					Alert.show("NativeProcess not supported");
				}

				// Read the last home used to start Tomcat (if any)
				var xml:XML = readConfig();
				if (xml != null)
				{
					javaHome.text = xml.javaHome;
					tomcatHome.text = xml.tomcatHome;
					return;
				}

				// If no known last home, present default/sample values
				if (Capabilities.os.toLowerCase().indexOf("win") > -1)
				{
					// default/sample values for Windows users
					javaHome.text = "C:\\Program Files\\Java\\jdk1.6.0";
					tomcatHome.text = "C:\\tomcat";
				}
				else if (Capabilities.os.toLowerCase().indexOf("mac") > -1)
				{
					// default/sample values for Mac users
					javaHome.text = "/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0";
					tomcatHome.text = "/Applications/tomcat/";
				}

			}

			public function startTomcat():void
			{
				// Write the Java and Tomcat home paths to config.xml so that they can be presented
				// to the user the next time the application is started
				writeConfig();

				log.text = "Starting Tomcat..." + File.lineEnding;
				startTomcatProcess = new NativeProcess();
				execute(startTomcatProcess, "start");
			}

			public function stopTomcat():void
			{
				log.text = "Stopping Tomcat..." + File.lineEnding;
				stopTomcatProcess = new NativeProcess();
				execute(startTomcatProcess, "stop");
			}

			public function execute(process:NativeProcess, arg:String):void
			{
				// Get a file reference to the JVM
				var file:File = new File(javaHome.text);
				if (Capabilities.os.toLowerCase().indexOf("win") > -1)
				{
					file = file.resolvePath("bin/javaw.exe");
				}
				else
				{
					file = file.resolvePath("Home/bin/java");
				}

				var tomcatHomeFile:File = new File(tomcatHome.text);

				// Start the process
				try
				{
					var nativeProcessStartupInfo:NativeProcessStartupInfo = new NativeProcessStartupInfo();
					nativeProcessStartupInfo.executable = file;
					nativeProcessStartupInfo.workingDirectory = tomcatHomeFile.resolvePath("bin");
					var processArgs:Vector.<String> = new Vector.<String>();
					processArgs[0] = "-Dcatalina.home="+tomcatHome.text;
					processArgs[1] = "-classpath";
					processArgs[2] = tomcatHomeFile.resolvePath("bin/bootstrap.jar").nativePath;
					processArgs[3] = "org.apache.catalina.startup.Bootstrap";
					processArgs[4] = arg;
					nativeProcessStartupInfo.arguments = processArgs;
					startTomcatProcess = new NativeProcess();
					startTomcatProcess.start(nativeProcessStartupInfo);
					startTomcatProcess.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA,
						outputDataHandler);
					startTomcatProcess.addEventListener(ProgressEvent.STANDARD_ERROR_DATA,
						errorOutputDataHandler);
				}
				catch (e:Error)
				{
					Alert.show(e.message, "Error");
				}
			}

			public function outputDataHandler(event:ProgressEvent):void
			{
				var process:NativeProcess = event.target as NativeProcess;
				var data:String = process.standardOutput.readUTFBytes(process.standardOutput.bytesAvailable);
				log.text += data;
			}

			public function errorOutputDataHandler(event:ProgressEvent):void
			{
				var process:NativeProcess = event.target as NativeProcess;
				var data:String = process.standardError.readUTFBytes(startTomcatProcess.standardError.bytesAvailable);
				log.text += data;
			}

			private function readConfig():XML
			{
				var file:File = File.applicationStorageDirectory.resolvePath("config.xml");
				if (file.exists)
				{
					var fileStream:FileStream = new FileStream();
					fileStream.open(file, FileMode.READ);
					var xml:XML = XML(fileStream.readUTFBytes(fileStream.bytesAvailable));
					fileStream.close();
					return xml;
				}
				else
				{
					return null;
				}
			}

			private function writeConfig():void
			{
				var xml:String = '<?xml version="1.0" encoding="utf-8"?>' + File.lineEnding;
				xml += "<config>" + File.lineEnding;
				xml += "<javaHome>" + javaHome.text + "</javaHome>" + File.lineEnding;
				xml += "<tomcatHome>" + tomcatHome.text + "</tomcatHome>" + File.lineEnding;
				xml += "</config>" + File.lineEnding;

				var file:File = File.applicationStorageDirectory.resolvePath("config.xml");
				var fileStream:FileStream = new FileStream();
				fileStream.open(file, FileMode.WRITE);
				fileStream.writeUTFBytes(xml);
				fileStream.close();
			}

			private function openWithDefaultEditor():void
			{
				var file:File =
					File.createTempDirectory().resolvePath("tomcat_launcher_console.txt");
				var fileStream:FileStream = new FileStream();
				fileStream.open(file, FileMode.WRITE);
				fileStream.writeUTFBytes(log.text);
				fileStream.close();
				file.openWithDefaultApplication();
			}


		]]>
	</fx:Script>

	<mx:Form width="100%">
		<mx:FormItem label="Java Home" width="100%">
			<s:TextInput id="javaHome" width="100%"/>
		</mx:FormItem>
		<mx:FormItem label="Tomcat Home" width="100%">
			<s:TextInput id="tomcatHome" width="100%"/>
		</mx:FormItem>
	</mx:Form>

	<s:HGroup>
		<s:Button label="Start" click="startTomcat()"/>
		<s:Button label="Stop" click="stopTomcat()"/>
		<s:Button label="Clear Console" click="log.text=''"/>
		<s:Button label="Open in Default Text Editor"
				  click="openWithDefaultEditor()"/>
	</s:HGroup>
	<s:TextArea id="log" width="100%" height="100%"/>


</s:WindowedApplication>
  • Pingback: Embedding Tomcat and BlazeDS in an AIR 2.0 Application()

  • Wow. Interesting use-case. This really opened my eyes to the usefulness of the Native Process API. Imagine having AIR delegate tasks which it cannot accomplish alone to Java, such as communicating via bluetooth, infrared, comm-ports and the like. Our imagination is the only limitation. Thanks for sharing

  • MZ

    Hi,
    although I have the latest (3.12.09 current) Air Runtime installed, I am getting the following error message:
    “This application requires an update to Adobe Air that is not avaiable for your system”
    Then the TomcatLauncher.exe does not start.
    Do you know why?
    TIA
    MZ

  • Pingback: AIR 2.0 Web Server using the New Server Socket API()

  • Henrik

    I really like this but could you please tell me how to open the project file on a mac. It would be nice if you could ditribute the source in another format. I haven’t found anything that takes a .fxp-file.

  • Henrik

    Sorry, I realized .fxp is the new FlashBuilder project format.

  • I think there is a copy-paste typo in your code. Line 066 should read

    execute(stopTomcatProcess, "stop");

    Note the name of the process passed.

    Stu

  • Pingback: AIR 2.0 Experimente » GELB der Powerflasher Blog()

  • Christophe,

    As always, thanks for the great examples.

    Quick question: since nativeProcess requires using a native installer, how would this affect applicationUpdaterUI, since it traditionally points to an .air file for updates?

    Can .air files be used to update an app after first install? It would seem strange to refer to an .exe in the update-config.xml file on the server, since if you’re supporting multiple platforms you’d need to point to three different installers…something I don’t think applicationUpdaterUI handles.

    Thanks for any thoughts and the great blog.

    Daniel

  • Cosma Colanicchia

    @Daniel,

    I had the same question, and found out that it simply doesn’t work.. quite a big issue for us, I hope that this is due to the beta status, and that it will be fixed in the final release.

    I filed a bug in the Adobe issue tracker: https://bugs.adobe.com/jira/browse/SDK-24824

    Please vote/suggest any workaround :)

    Cosma

  • Pingback: Examples of using the new AIR 2.0 Native Process API « Z3bbster()

  • Pingback: Examples of using the new AIR 2.0 Native Process API « Z3bb Blog()

  • Pingback: links for 2010-05-26 « sySolution()

  • Osh Evans

    Been using AIR for a while, and only recently needed to use native processes… thanks for the post.

    Worth noting that you can listen to the NativeProcessExitEvent.EXIT if your running build processes (e.g. I’m executing the mxmlc and adl processes and needed to listen to the mxmlc exit before firing off the adl process).

  • Thanks for this article, I could embed the Red5 into AIR and to get possibility to record the video in AIR locally http://mydevrecords.blogspot.com/2012/01/local-recording-in-adobe-air-using-red5.html

  • This is quite interesting thank you. i think my blog is a bit related to yours. I hope it may help you in the future .

css.php