Migrating to Apache Wicket – GIDS presentation slides

The ‘Migrating to Apache Wicket’ presentation I made at the Great Indian Developer Summit last week went off well. It was great to see the interest in Wicket, the room was full and there were quite a few questions even during the course of the session before I got to the end.

Here are the slides for your viewing pleasure. Apologies if some of the slides look cluttered because the animations I used to talk through some of the slides have been lost after converting to PDF.

Here below is one of the images I used to illustrate how a project that starts out using an action-oriented MVC framework with JSP can end up with a ‘Franken-stack’ over time. It starts innocently enough, you decide to use something extra say to enforce a common layout, then you realize you need some third party tag-libs, then security, and of course Ajax and before you know it…

What if a framework exists that can do all of this in Just Java and Just HTML? Find out more.

Hope you find the slides useful. I look forward to your feedback and suggestions on how the content can be improved. Please do comment!

How to record RTMP flash video streams using Red5

Update 2009-04-04: I’ve released an open source project that you can use to download RTMP video streams in a much simpler way. You can find it here: http://flazr.com

Red5 is an open source Flash server written in Java. It does not include a standalone client yet but I was able to write a Java program that uses Red5 to connect to an RTMP video stream and record / save it to a file. Code is provided below and also some tips on how to get the details required to download flash videos that you come across on the internet.

Here’s the code of “MyRtmpClient.java”, you are free to use and modify this in any way you want.

import java.io.File;

import org.apache.mina.common.ByteBuffer;
import org.red5.io.IStreamableFile;
import org.red5.io.ITag;
import org.red5.io.ITagWriter;
import org.red5.io.flv.impl.FLVService;
import org.red5.io.flv.impl.Tag;
import org.red5.io.utils.ObjectMap;
import org.red5.server.api.event.IEvent;
import org.red5.server.api.event.IEventDispatcher;
import org.red5.server.api.service.IPendingServiceCall;
import org.red5.server.api.service.IPendingServiceCallback;
import org.red5.server.net.rtmp.Channel;
import org.red5.server.net.rtmp.RTMPClient;
import org.red5.server.net.rtmp.RTMPConnection;
import org.red5.server.net.rtmp.codec.RTMP;
import org.red5.server.net.rtmp.event.AudioData;
import org.red5.server.net.rtmp.event.IRTMPEvent;
import org.red5.server.net.rtmp.event.Notify;
import org.red5.server.net.rtmp.event.VideoData;
import org.red5.server.net.rtmp.message.Header;
import org.red5.server.net.rtmp.status.StatusCodes;
import org.red5.server.stream.AbstractClientStream;
import org.red5.server.stream.IStreamData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyRtmpClient extends RTMPClient {

	private static final Logger logger = LoggerFactory.getLogger(MyRtmpClient.class);

	private String saveAsFileName = "test.flv";

	public static void main(String[] args) {

		String host = "localhost";
		String app = "oflaDemo";
		final String name = "IronMan.flv";
		int port = 1935;
		final int duration = 10000; // milliseconds, -2 means until end of stream

		final MyRtmpClient client = new MyRtmpClient();
		logger.debug("connecting, host: " + host + ", app: " + app + ", port: " + port);
		
		IPendingServiceCallback callback = new IPendingServiceCallback() {							
			public void resultReceived(IPendingServiceCall call) {
				logger.debug("service call result: " + call);
				if ("connect".equals(call.getServiceMethodName())) {					
					client.createStream(this);
				} else if ("createStream".equals(call.getServiceMethodName())) {													
					Integer streamId = (Integer) call.getResult();
					logger.debug("createStream result stream id: " + streamId);
					logger.debug("playing video by name: " + name);						
					client.play(streamId, name, 0, duration);
				}								
			}						
		};
		
		client.connect(host, port, app, callback);

	}

	private RTMPConnection conn;	
	private ITagWriter writer;

	private int videoTs;
	private int audioTs;

	@Override
	public void connectionOpened(RTMPConnection conn, RTMP state) {
		logger.debug("connection opened");
		super.connectionOpened(conn, state);
		this.conn = conn;
		init();
	}

	@Override
	public void connectionClosed(RTMPConnection conn, RTMP state) {
		logger.debug("connection closed");
		super.connectionClosed(conn, state);
		if (writer != null) {
			writer.close();
			writer = null;
		}
		System.exit(0);
	}

	@Override
	public void createStream(IPendingServiceCallback callback) {
		logger.debug("create stream");
		IPendingServiceCallback wrapper = new CreateStreamCallBack(callback);
		invoke("createStream", null, wrapper);
	}

	@Override
	protected void onInvoke(RTMPConnection conn, Channel channel, Header header, Notify notify, RTMP rtmp) {
		super.onInvoke(conn, channel, header, notify, rtmp);		
		ObjectMap<String, String> map = (ObjectMap) notify.getCall().getArguments()[0];
		String code = map.get("code");
		if (StatusCodes.NS_PLAY_STOP.equals(code)) {
			logger.debug("onInvoke, code == NetStream.Play.Stop, disconnecting");
			disconnect();
		}
	}	

	private void init() {
		File file = new File(saveAsFileName);
		FLVService flvService = new FLVService();
		flvService.setGenerateMetadata(true);
		try {
			IStreamableFile flv = flvService.getStreamableFile(file);
			writer = flv.getWriter();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private class CreateStreamCallBack implements IPendingServiceCallback {

		private IPendingServiceCallback wrapped;

		public CreateStreamCallBack(IPendingServiceCallback wrapped) {
			this.wrapped = wrapped;
		}

		public void resultReceived(IPendingServiceCall call) {
			Integer streamIdInt = (Integer) call.getResult();
			if (conn != null && streamIdInt != null) {
				MyNetStream stream = new MyNetStream();
				stream.setConnection(conn);
				stream.setStreamId(streamIdInt.intValue());
				conn.addClientStream(stream);
			}
			wrapped.resultReceived(call);
		}

	}

	private class MyNetStream extends AbstractClientStream implements IEventDispatcher {

		public void close() { }

		public void start() { }

		public void stop() { }

		public void dispatchEvent(IEvent event) {
			if (!(event instanceof IRTMPEvent)) {
				logger.debug("skipping non rtmp event: " + event);
				return;
			}
			IRTMPEvent rtmpEvent = (IRTMPEvent) event;
			if (logger.isDebugEnabled()) {
				logger.debug("rtmp event: " + rtmpEvent.getHeader() + ", "
						+ rtmpEvent.getClass().getSimpleName());
			}
			if (!(rtmpEvent instanceof IStreamData)) {
				logger.debug("skipping non stream data");
				return;
			}
			if (rtmpEvent.getHeader().getSize() == 0) {
				logger.debug("skipping event where size == 0");
				return;
			}
			ITag tag = new Tag();
			tag.setDataType(rtmpEvent.getDataType());
			if (rtmpEvent instanceof VideoData) {				
				videoTs += rtmpEvent.getTimestamp();
				tag.setTimestamp(videoTs);
			} else if (rtmpEvent instanceof AudioData) {
				audioTs += rtmpEvent.getTimestamp();
				tag.setTimestamp(audioTs);
			}
			ByteBuffer data = ((IStreamData) rtmpEvent).getData().asReadOnlyBuffer();
			tag.setBodySize(data.limit());
			tag.setBody(data);
			try {
				writer.writeTag(tag);
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
	}

}

To compile and run the code you need the following libraries:

commons-collections-3.2.jar
jcl104-over-slf4j-1.4.3.jar
logback-classic-0.9.8.jar
logback-core-0.9.8.jar
mina-core-1.1.6.jar
red5.jar
slf4j-api-1.4.3.jar
spring-beans-2.0.8.jar
spring-context-2.0.8.jar
spring-core-2.0.8.jar

We need the latest Red5 JAR built from version control (not the latest official release) and I have uploaded these files here: red5-rtmp-client-libszip.pdf – just rename the file to end with “.zip” after downloading.

The code shown is hard-coded to connect to “localhost”, app name “oflaDemo” and stream name “IronMan.flv” which can be tested on the official Red5 server distribution which you can download and run. When it comes to downloading flash streams from the internet, WireShark can be used to sniff out the values you need.

Let’s take this site for example: http://videolectures.net/ Start a WireShark capture session before clicking on a video on the site to play it. Let WireShark grab all the information exchanged between your PC and the remote flash server and you can stop the capture once the video begins to play, we are only interested in what goes on during the connection handshake. I will use this video as an example: http://videolectures.net/ff06_chomsky_szmp/ [update Jun-2008: looks like they changed this particular video to Windows media instead of Flash, so try other videos or other sites]

In WireShark you can filter for protocol “rtmpt” and the first few entries would be handshake or “invoke” operations. Examining the “Handshake part 3″ we can easily get the value of the required “app” property. Below we can see it is “video/2006/other/ff06/chomsky_noam”:

For the host name, the IP address should do fine for most sites, but we can easily figure out the host name of the stream server from what appears after “rtmp://”. Note that WireShark allows you to search the text contents of captured packets. Here below we can see that the host name is “velblod.videolectures.net”:

And finally when the “play” command is issued – we need the value of the stream name. Below we see it is “chomsky_noam_01″:

So with the right values of hostname, app and stream name set – you can run the program and download the stream to your local drive for offline viewing. To download the whole stream – just change the duration to ‘-2′ as hinted in the source code comment. There are many free Flash players available you can use to play downloaded content such as FLV Player.

Do let me know if this works for you and if you find any additional parameters that need to be passed for other sites.

Never install NetBeans again! (just use the ZIP)

Okay that title was just to grab your attention :) What I mean is that I’m going to show you how to use the NetBeans ZIP distribution on Windows so that you never need to use the installer executable again. If you are like me and would like total control over the folders that NetBeans uses, or if you hate installing things onto Windows because of all that stuff that gets added to the programs menu and your user profile folders and maybe your registry – you will probably like this approach. Now that NetBeans 6.1 release candidate
is out, this is a good way for you to try it out in an un-intrusive manner especially if you have an older version of NetBeans already installed.

Although most people use the platform specific installers, NetBeans can also be downloaded as a platform-independent ZIP file. You can get the various flavors here for 6.1 RC: http://dlc.sun.com.edgesuite.net/netbeans/6.1/rc/zip/

I am going to use the “javase” version which is just 50MB. I can’t help mentioning that two years back, one of the main reasons I switched to NetBeans was because I felt that Eclipse was getting too bloated for comfort. Never regretted it, and especially now that Apache Wicket is my web-framework of choice – the NetBeans “javase” version is just perfect because Wicket only needs HTML and Java editing support.

After downloading the file, in this case called “netbeans-6.1rc1-200804100130-javase.zip” extract it to any folder of your choice. Create a batch file (you can call it “netbeans.bat”) and place it in the top level “netbeans” folder itself with the following contents:

set NETBEANS_HOME=%~dp0
set NETBEANS_HOME=%NETBEANS_HOME:~0,-1%

set JAVA_HOME=C:\peter\app\jdk1.6.0_02
set NETBEANS_USER_DIR=C:\peter\workspaces\netbeans

start /b %NETBEANS_HOME%\bin\netbeans.exe --jdkhome %JAVA_HOME% --userdir %NETBEANS_USER_DIR%

The first two lines are DOS hacks to obtain the path of the current directory (without a trailing slash) where you un-zipped NetBeans. You should point the JAVA_HOME to the JDK you wish to use. And finally, you can customize the folder where NetBeans saves your settings (which by default goes somewhere into your “Documents and Settings” folder). The “start /b” part is a way to make the console window “disappear” after NetBeans launches otherwise you may terminate it later and kill NetBeans by mistake.

That’s it! Now just double-click on the batch file and NetBeans will start and even create the “userdir” if it does not exist. I actually use NetBeans like this on a daily basis. It should be easy to evaluate NetBeans this way – you don’t have to go through any un-install step (in the unlikely event that you don’t like NetBeans :)

This is a nice way to control which JDK NetBeans uses, which is useful in case you have multiple JDKs installed. I use this approach for conducting training workshops where I provide a ZIP that includes NetBeans, a custom bundled JDK and a sample project – the batch file uses relative paths, just unzip and you are good to go.

If you have a NetBeans tip to share – enter the NetBeans blogging contest here: http://www.netbeans.org/competition/blog-contest.html?cid=923686

Fibonacci spiral fun with Java3D

Recently I became fascinated with the Fibonacci series and the Golden Ratio, especially with how they turn up in nature. I had been meaning to explore the Java3D API for a while, so I thought it would be a good idea to try and generate some Fibonacci spirals and get to know Java3D that way. Here is one example of what I could come up with.

fibo_java3d_1

I was able to tweak the code to highlight interesting patterns. I actually thought for a moment that I had “discovered” a few things like for instance how if you number the “dots” by the order in which they are introduced into the simulation, the spirals that visually strike you are formed by joining the dots where the number difference is a fibonacci number.

Of course all this is common knowledge in the mathematics community. But I think I was able to create much prettier visualizations :) Here is a snapshot showing how the two most obvious spirals are where the dot-number difference is equal to 13 and 21 (successive Fibonacci numbers). Dot number 1 is on the far right.

fibo_java3d_2

And here is this interesting effect when the dots are numbered in reverse order, the dots corresponding to the Fibonacci sequence (1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144) line up along the x-axis.

fibo_java3d_3

Well, this is animation after all – so here is my first ever YouTube video :) If you are interested, here is the source code (not much documentation I’m afraid).

I found the fantastic sunflower photo on Flickr. The photo description has a good explanation of why the fibonacci spirals happen and the maths behind it. If you want to explore more, have a look at this Science News Online article.

Update 2008-12-13: Found this link to some nice mathematical analysis of number spirals (not Fibonacci): http://www.numberspiral.com/

Japanese anime song and dance stuck in my head

Thanks to Boing Boing for introducing me to this kind of stuff. It all started with this Haruhi Suzumiya dance, I watched the YouTube clips and sure enough, that was enjoyable. Exactly as predicted by the BB poster, the song stuck in my head for a week.

Then just recently there was this: “Software creates songs, vocals from text“. Woah, cool. You gotta hand it to the Japs!

So I followed the links, listened to the demos, and I was thrilled. Will this “Vocaloid” ever be available in English? I’ll buy it!

Something about the “anime song generator” stuff got to me, I feverishly searched for more demos. Soon, I landed on this YouTube video – a song called “Go My Way”. Yes, the vocals are computer generated!

[update: looks like the YouTube video was taken down because of some effing “terms of use” violation. I was able to find a version minus the video of the cute dance here: http://www.youtube.com/watch?v=9g5Ezdml3ww]

This was getting better! Although the video quality is terrible, the choreography, the cute anime characters and the CGI camera moves really got my attention. My, what a catchy tune! Little did I know that this song and dance was going to turn me into a crazed file-downloading j-pop-tune-humming maniac over the next two days or so.

Digging around for the source of the video, I came to learn about this Japanese arcade game called “iDOL@MASTER“. The game is about building the career of a cute anime “Japanese Idol” contestant you get to choose at the start. This Wired blog post has details on the gameplay.

So apparently in the game, once you are done with the mini-games and the sim gameplay, you sit back and watch as your precious teen idols take the stage and perform. I’ll probably never have the patience to play the whole game, but the computer generated performances and one particular song and its choreography is what I am currently obsessing about.

The in-game songs are voiced by humans and the YouTube clip above is just a Vocaloid demo. I watched all the YouTube clips of the iDOL@MASTER stage performance routines I could get and this “Go My Way” song was by far the catchiest. And those dance moves! The glorious cel-shaded animation! The smooth, sweeping camera moves and changes! The super-cute anime girls! The hair and costumes! The stage, lighting effects and sets! Wow. Very soon I had found hi-res versions of “Go My Way” and a couple other iDOL@MASTER songs on the net. Awesome.

So have I gone stark raving mad? In my defense, here is a YouTube clip I found of a grown man showing off his mastery over the “Go My Way” dance moves in sync with the video. Heh.

My cousin will be proud of me, he was trying to introduce me to the wonderful world of Japanese anime a while back and I still have those “Naruto” episodes on my hard disk. Maybe I’ll go back and finish watching them all. So much to do, so little time. *sigh*

Now coming full circle, I explored more about Haruhi Suzumiya – intriguing stuff, maybe will watch someday. But allow me to rave about this one particular video clip from the TV series where the main character and her friends play a rock-song live in a high-school event.

Considering that this is animation, the visual accuracy of how the characters are depicted playing the instruments in sync with the music – is to be seen to be believed. I wonder if the animators did something like rotoscoping, but the video captures the mood of a live band and the pent-up emotion of the lead singer amazingly well. The song is called “God Knows”, you can search for hi-res versions on the net.

In time, I’ll probably look back at this blog post and say to myself “what was I thinking?” but this stuff has so gotten under my skin that I simply have to post about it! “Go My Way” is well and truly stuck in my head and is not showing any signs of going away, I suspect because of the additional visual appeal of the dance moves. Oh help.

“Go My Way” is the best pop song EVAR!

I hope to be back to posting on Java soon. Heh.

A Wicket user tries JSF

A year or so back I attempted to learn JSF and even bought one of the recommended books. But after reading through the first hundred or so pages, I came to the conclusion that it was too complicated and un-intuitive. The code samples looked ugly to me in a way that was difficult to explain. The fact that there was a different expression language (with a ‘#’ instead of ‘$’) to get used to when I was just getting comfortable with JSP EL did not help either.

So I gave up trying to learn JSF and decided to concentrate on Spring MVC at that time.

Fast forward to today and the main trigger for my current experiments with JSF is because after my recent blog post on migrating from Spring MVC / WebFlow to Wicket, I am increasingly being asked whether I ever tried JSF.

So I have attempted to write a very simple application (based on MyFaces) and I will try to compare it side by side with the exact same functionality implemented in Wicket. I just started with JSF a couple of days back so feel free to comment and let me know if I missed something fundamental and I will update things either in this post or the next.

Functionality
This is supposed to be a “discussion forum” application and there is only one domain object called “Forum” with two String fields “name” and “description”. Maybe in the future I will include “Topic” and “Message” objects but for now I’m trying to keep things as simple as possible.

screens.png

There are only two screens. The home page is a list of Forum(s) in the system. There is a link to create a new Forum. This brings up the other screen which is a form with two text fields and a submit button. After a successful submission, you are taken back to the home page and the newly created Forum appears in the list.

Environment
NetBeans 5.5 is being used. I deploy to a Jetty instance that I set up manually because I want to also compare the Jetty “footprint” required for Wicket vs MyFaces. With some digging I’ve tried to arrive at the bare minimum “quickstart” config required, for example the web.xml file cannot be possibly smaller. I’m using Maven2 directory and naming conventions. Defaults are used as far as possible. In the side by side comparison images below JSF is on the left and Wicket is on the right.

So here goes:

Project Structure
project-structure.png

  • Wicket does not require any extra XML config like you have faces-config.xml for JSF
  • Wicket does not require JSPs
  • By default, Wicket expects the markup (*.html files) to be side by side with the *.java files corresponding to components – e.g. Page(s). Some people find this a bit odd but this keeps things simple and there are some very good reasons for this. You can change this convention if you really want to.
  • The MyFaces application requires an “index.jsp” welcome-file to redirect to the virtual home page URL

Jetty Footprint
jetty-footprint.png
Since Wicket does not require JSP support – the Jetty footprint is much smaller – the contents of the Jetty “lib” folder come down from 6.16 MB to 692 KB

Library Dependencies
dependencies.png
Even without using Tomahawk etc., MyFaces requires far more dependencies than Wicket i.e. 10 vs 4 JAR files

faces-config.xml
faces-config-xml.png
Wicket does not require any kind of XML configuration at all and the “faces-config.xml” file does not have any equivalent. Navigation logic is performed in pure Java code which is much more flexible and type-safe and you don’t need to synchronize multiple files.

web.xml
web-xml.png
A servlet filter handles all the framework stuff in Wicket. This has some advantages like you can map the root of your context path “/” to the home page. Anything not meant for the Wicket filter like images, css reources etc. will be ignored by the filter.

You need to tell Wicket where your “Application” class is located. This Application class is used primarily to point to the designated home page.

The Wicket Application Class
wicket-application-class.png
In a real life application, this is where you would manage references to the service-layer, configure security and various other things. Here we are just configuring the home page.

The “List Forums” Screen
forum-list-screen2.png

  • The wicket markup is pure HTML but the MyFaces JSP is a mixture of JSP, HTML, JSF tags and JSF EL
  • In the case of Wicket you can see that the complexity clearly moves out of the markup into the Java code. In the JSF version, the only thing the backing class is doing is setting up the model / list
  • You don’t need a specialized IDE to edit the Wicket version – HTML editing and Java editing support is sufficient. In the case of JSF you have to rely on IDE support when the UI gets complex
  • If you make a spelling mistake in the JSF EL expressions – you won’t know until run time – but the Java code that renders dynamic data in the Wicket version does not have that problem, you benefit from your IDE syntax coloring, refactoring suppport etc.
  • The JSF version is using a “dataTable” control. For Wicket we are using a ListView repeater control and creating a table manually. Even then, this is very simple code and you have much more control over the markup. The JSF dataTable documentation is not for the faint of heart and runs into a few pages. That is a lot of complexity you don’t need most of the time. BTW Wicket does provide a range of grid controls in the Wicket extensions project
  • Instead of navigation rules defined in faces-config.xml and the need to synchronize this with the action /event names you use in your JSP or backing class – in Wicket you can easily navigate from page to page by using the right “Link” components or setResponsePage()
  • Instead of using “id” which has a lot of significance in terms of (X)HTML – Wicket uses a namespace so “wicket:id” is the attribute you use to link your Java code and markup. Relying on the “id” attribute can lead to all kinds of unpleasant name collisions in JSF.

The “Create New Forum” Form
forum-form-screen3.png
In addition to the points in the previous section:

  • In JSF you declare and configure components in the markup and you have to use the right tags (e.g. h:form, h:inputText, h:commandButton) and the right attributes (e.g. to mark an input field as “required”). JSF is supposed to be a component oriented framework but I wouldn’t call them Java components and definitely not Object Oriented.
  • In Wicket you use normal HTML tags in the markup and in the Java code, you instantiate and attach the core components (e.g. TextField) or extend them as required (e.g. Form). All the behavior, and things like setting a field as “required” happen in pure Java code.
  • In this case for Wicket the value of the “wicket:id” attribute on the input text fields also serves as OGNL notation for HTML form –> POJO binding – which reduces the amount of configuration.
  • Rather than rely on a h:panelGrid to layout the form fields it is easier and more intuitive to use plain old HTML tables for this purpose.
  • The screenshot below demonstrates how the “ForumFormPage.html” file looks like when opened in a browser. Being valid HTML, Wicket markup files can be previewed and edited as such. Wicket provides for true separation of concerns and HTML screen design and layout can *really* be done by a team separate from the Java development team

browser-preview.png

Generated HTML
The HTML generated by MyFaces is unbelievably verbose and contains all kind of strange javascript which I never, ever would have expected. If there is one reason to stay away from MyFaces, this is it.

You can click on the links below to see the output. If your browser renders the HTML instead of displaying the raw text, just do “view source”.

For Wicket, the generated HTML is clean like you would expect. And note that the Wicket specific attributes like “wicket:id” will be stripped in PRODUCTION mode.


Update [2007-06-10]: Simplified the wicket code for the “Create New Forum” form after it dawned on me that the existing POJO is more than sufficient for form binding. The older version can be found here: forum-form-screen2.png

The Java code for the Wicket form page can be made even more tighter using an anonymous inner class for the form. This is a nice example of how Wicket gets you to exercise the Java OO parts of your brain, and it is worth comparing this with the “ForumFormPage.java” above.

Update: I uploaded the Java and HTML source for the 2 Wicket pages. Since WordPress has restrictions on uploads (no *.zip allowed for e.g.), you have to rename the files correctly if you download them – Forum List Page: HTML / Java | Forum Form Page: HTML / Java

How to do String operations in Ant

I was trying to create an Ant based wizard to generate a Wicket quickstart project. So like the trend nowadays the script should prompt you for the project name at the start then *boom* – you get your Maven2 directory structure, pom.xml, web.xml, a couple of placeholder web-pages – just type mvn jetty:run and you are up and running.

I wanted the user to only enter the project-name: a lower-case string without spaces or special characters. Then I wanted the Wicket Application class to be generated as per tradition i.e. if your project short name is “test” the file should be “TestApplication.java”

But then it hit me – there is no way to do things like uppercase / lowercase string conversion in Ant! Yikes. I could make-do with a second prompt for the user to get the application class name, but hey, that’s so not-DRY, and I like an Ant challenge :)

One option was to use Beanshell – but I’m obsessed with getting things to work with a “clean” Ant install, without messing around with the classpath or ant-home. After being spoilt by Maven, I don’t like having orphan jar files lying around in the project directory structure either.

So I ended up doing this. Here is a small Java class that processes a command line argument and writes the result to a file:

import java.io.FileOutputStream;
import java.io.PrintWriter;

public class TitleCaser {

    public static void main(String[] args) throws Exception {
        FileOutputStream os = new FileOutputStream("target/temp.txt");
        PrintWriter out = new PrintWriter(os);
        String s = args[0];
        out.write(Character.toUpperCase(s.charAt(0)) + s.substring(1));
        out.close();
        os.close();
    }

}

This java file resides in an “etc” folder. And the start of the Ant script looks like this:

<javac srcdir="etc" includes="TitleCaser.java"/>        
<input message="Enter Project Name:" addproperty="project.name"/>
<java classpath="etc" classname="TitleCaser">
     <arg value="${project.name}"/>
</java>
<loadfile property="project.name.titleCase" srcFile="target/temp.txt"/> 

I know, I know, Ant is XML, it is a declarative language, I must be crazy etc. But I can think of all kinds of interesting things to do with this approach. There are various ways to load stuff into Ant from files, “loadfile” is just one of them. Think of the possibilities – you can generate Java code from Ant, “javac” it on the fly and work around all the Ant limitations like not having good control-flow support etc. Maybe you can even generate some XML and use that in a downstream target and tell your boss that your Ant build is SOA compliant…

Follow

Get every new post delivered to your Inbox.

Join 31 other followers