Friday, August 29, 2008

Myth: Java widely used in the browser

Yes I aspire to be a MythBuster, don't we all? For a while now there has been some concern over Apple's apparent lack of love for Java. Lately the debate has shifted over to the iPhone and the Java blogosphere is doing its best to send a message to Apple. While I am far from a supporter of the policies and practices of Apple, I do think they are right in determining Java as legacy or an anomaly on the web of 2008.

A sniffing bot
It turns out it's hard to find concrete evidence of how widespread the use of Java client technology is on the internet. What we can do however, is simulate a web user, randomly browsing around and taking note of how often we bump into Java.
The first issue is, how can we come up with truly random URL's? Three different approaches comes to mind:

  1. Use some existing randomizer service

  2. Methodically run through IP ranges

  3. Screen scrape Google results

I actually implemented all 3. Of those both item 2 and 3 potentially raises some ethical questions, consistent scanning of remote systems can be considered a hostile activity as can web scraping. Thus the one presented here uses the 1'st approach only and is quite slow due to the fact that the random function of the site I am using seems to be bound by timestamp data (probably works by hashing the hh:mm:ss and looking up an index in a table). For that reason, and out of politeness, I've inserted a 1 sec delay between each attempt.

The scent of Java
Due to Java's age, there are quite a few ways it can be embedded onto a page depending on both HTML standards and individual browsers. There the APPLET tag, there's the OBJECT tag and then there's the EMBED tag. This should also include the recent iteration of Java client technology, namely JavaFX, since they appear to be reusing the APPLET tag for this - odd actually, considering W3C/HTML4.x deprecated this tag 9 years ago now.

Apart from these tags, JavaScript and frames can further hide the fact that Java technology is being used. This is not very common from what I've experienced though, much more common with Flex and especially Silverlight. Also we need to take into consideration Java Web Start, we do this by capturing links which points at *.jnlp files.

Implementation
Was pretty trivial, thanks to Java Swing's old but readily at hand HTMLEditorKit. The hardest part was coming up with a simple yet functional query language as I did not want to hardwire these rules nor depend on any 3'rd part library. The essence of the markup matching is modeled by an enum (I love Java enum's) and it looks like this:


enum TagMatch{
   
JAVA(// Most Java applets exist inside an APPLET tag
        Tag.create(HTML.Tag.APPLET, Attr.create(HTML.Attribute.CODE)),
        
// Java Web Start will be anchors ending in .jnlp
        Tag.create(HTML.Tag.A, Attr.create(HTML.Attribute.HREF, ".*\\.jnlp$")),
        
// OBJECT and EMBED tags pass in class or jar file
        Tag.create(HTML.Tag.PARAM, Attr.create(HTML.Attribute.VALUE, ".*\\.(class|jar)$"))
   
);
    
   
private List<Tag> tagMatches = new ArrayList<Tag>();

   
TagMatch(Tag... tagVararg){
       
tagMatches = Arrays.asList(tagVararg);
   
}

   
public List<Tag> getTagMatches(){
       
return tagMatches;
   
}
}


If you want to take a look at the whole source file, it's available righ here.

Enough with the talk
The following is the result of scanning 1.000 random sites:


Found 7 sites matching JAVA
+--[www.gailzavala.com/]
+--[www.nationalhealthcouncil.org/]
+--[web2c.com]
+--[armadaweb.com/Anime/Sailor/]
+--[www.gloverpainting.com]
+--[www.theschrodts.com]
+--[members.shaw.ca/micheil/burns/burns.htm]



Well well, 7 sites out of 1000 were identified as using Java on the client tier. I have of course run it a number of times, each resulting in roughly the same numbers somewhere between 5 and 10. This leaves me to conclude that the amount of sites using Java is less than 1%. And how about those that do? If you take a look at some of the links, at least half of them are webcam viewers, scrollers and counters reminiscent of the 90's.

Conclusion
Can't say I was surprised by the result. I am aware of a few things that relies on Java (home banking comes to mind) but then again, I also know of minority sites that use ActiveX. So I still think we can not slam Apple for this one. Certainly iPhone users would be missing Flash much more than Java. Of course, let's hope the new JavaFX stuff and the forthcoming update 10 will pan the way for a renascence.

Update:
People don't seem to like this entry very much. I'm not sure whether its because I am speaking negative about Java in the browser or if I simply didn't convey the message good enough. So to rephrase, this is a simple test to show that Java applets and web start applications are in such low use on websites that to most people (and by inference browser vendors) not having support for these doesn't matter much. Contrast this to the results I get when running another test, this time searching 1000 random sites for for Flash/Flex usage (tags with a SRC attribute containing ".swf"). Such a query results in the following matches:


Found 179 sites matching FLASH
+--[www.countrybumpkin.com.au/]
+--[www.chrissmithphoto.com]
+--[www.bostonbw.com]
+--[www.lipseylipsey.com]
+--[www.zewa.com]
+--[www.webguide.com/attractions.html]
+--[www.socaldelivery.com]
+--[www.lincolnspeedway.com]
+--[www.redcliffslodge.com]
+--[www.baldwin-school.org/]
+--[www.bdainc.com]
+--[www.hydraulicpumps.co.uk/]
+--[www.mainstreetcottonshop.com]
+--[www.yellowstonevacations.com]
+--[www.acmotorcars.com]
+--[www.deadwooddicks.com]
+--[www.judycarter.com]
+--[biz.yahoo.com/n/l/s/sun.html]
+--[www.siasat.com]
+--[www.jackand.co.jp/]
+--[www.meritax.com]
+--[www.westpasco.com]
+--[www.calpers-governance.org/]
+--[www.paradiseranch.com]
+--[www.tamilnaduwomen.org/]
+--[www.dynamitedetail.com]
+--[sportsillustrated.cnn.com/basketball/ncaa/men/teams/oregon/]
+--[www.glenfir.com]
+--[www.marquette.org/]
+--[www.uwwsports.com/index.asp?path=football]
+--[www.sfballoonguy.com]
+--[www.alliedmodular.com]
+--[www.helenelliott.com]
+--[www.nationwidemortgage.com.au/]
+--[www.admissions.umich.edu/]
+--[www.globalcredit.com]
+--[www.godinos.com]
+--[www.leonidas-chocolate.com]
+--[www.01-flash-web-templates.com]
+--[www.gwmcnamara.com]
+--[www.HiDoctor.com]
+--[www.metromayors.org/]
+--[www.vigorita.com]
+--[www.goforth.org/]
+--[www.reitdesign.com]
+--[www.brentlmiller.com]
+--[www.abingdon.com]
+--[www.ravenwoodgolf.com]
+--[www.goldner.com]
+--[www.sarasota.k12.fl.us/brookside/]
+--[www.craigsmithrv.com]
+--[www.labelexpress.com.au/]
+--[www.bagsoflove.co.uk/]
+--[www.var-provence.com/web_anglais/]
+--[www.santafe.cc.fl.us/]
+--[www.terrybryant.com]
+--[www.philanthropy.iupui.edu]
+--[www.kkla.com]
+--[www.iconicimaging.co.uk/]
+--[www.christielodge.com]
+--[www.waynegoodwin.org/]
+--[www.livingdesert.org/]
+--[www.scblues.com]
+--[www.skippinggirl.com]
+--[www.thebusinessgarden.co.uk/]
+--[www.armbrusterrealty.com]
+--[www.butterfly-kisses.com]
+--[www.acceptancefinancial.com]
+--[www.nvhomes.com]
+--[www.gerlinglaw.com]
+--[www.aweuk.com]
+--[www.uintagolf.com]
+--[www.lawinc.com]
+--[www.neteffects.com]
+--[www.coe.ufl.edu/]
+--[www.key2holidays.co.uk/]
+--[www.sachem.k12.ny.us/]
+--[asia.weather.yahoo.com/asia/China/]
+--[www.ecofurn.com.au/]
+--[www.breckenridgeskishop.com]
+--[www.camroadproperties.com]
+--[www.saassociates.com]
+--[www.cabrillo-inn.com]
+--[www.wwf.org.nz/]
+--[www.kenilworthinn.com]
+--[www.marinedivers.com]
+--[wagner.rivals.com]
+--[www.kanales.gr/]
+--[www.tourinfo.ru]
+--[www.krischs.com]
+--[www.visitlaportecounty.com]
+--[www.roninyachtcharters.com]
+--[www.utp.ac.pa/]
+--[www.colmet.com]
+--[www.rolltidebama.com]
+--[www.automotivecad.co.uk/]
+--[www.titancomics.com]
+--[www.breakfastclub80.com]
+--[www.arowanaclub.com]
+--[www.parachuteschool.com]
+--[www.kraft.com.au/]
+--[www.insuranceoffice.com]
+--[www.sbpolo.com]
+--[www.jlg.com/jlg/products/product-center.html]
+--[www.whmis.net/]
+--[www.theharleystreetclinic.com]
+--[www.rimatravel.co.uk/]
+--[www.uppercaperealty.com]
+--[www.cranecare.ltd.uk/]
+--[www.hostingpartners.co.uk/]
+--[www.jawfixer.com]
+--[www.telemoneyworld.com]
+--[www.minaret.vic.edu.au]
+--[www.houstontexaslaw.com]
+--[www.noisepop.com/2003/]
+--[www.crosswind.ms/]
+--[www.sweethomestexas.com]
+--[www.kahaku.go.jp/]
+--[www.keyualcohol.com]
+--[www.sdb.com]
+--[www.fgcswim.org/]
+--[www.quesnelcurlingclub.com]
+--[www.eggsolutions.ca/]
+--[www.msmedia.com.au]
+--[www.revesbypress.com.au/]
+--[www.strategicevents.com]
+--[www.anabenitez.com]
+--[www.ldai.com]
+--[www.c21capital.ca/]
+--[www.olympic.si/]
+--[www.mma.org.my/]
+--[www.markspencer.com]
+--[www.tdfund.com]
+--[www.residence-hotel.com]
+--[www.loanguy.com]
+--[www.middletonmann.freeserve.co.uk/]
+--[www.dallashomesales.com]
+--[www.firstcolorado.com]
+--[www.predatorsoftheheart.com]
+--[www.adtechintl.com]
+--[www.bleibtreu.com]
+--[www.kurt-darla.com]
+--[www.dreamkitchens.com]
+--[www.artsaloft.com]
+--[www.Theoakridgeschool.org/]
+--[www.maharashtratourism.gov.in/]
+--[www.lampson.com]
+--[www.token.com.tw/]
+--[www.panoptic-online.com]
+--[www.cbpacific.com]
+--[www.stricklandchevrolet.com]
+--[www.jamesnewhouselaw.com]
+--[www.vlada.hr/]
+--[www.calc.com]
+--[www.mustakbil.com]
+--[southwestutahfilm.com]
+--[www.sackville.com]
+--[www.bonbonniere.nl/]
+--[www.lizzydesign.com]
+--[www.ambientjobs.com]
+--[www.nola.com/lsu/]
+--[www.greenbergchiropractic.com]
+--[www.donsphoto.com]
+--[sports.espn.go.com/ncf/clubhouse?teamId=324]
+--[www.dangleterrehotel.com]
+--[www.mcsellshomes.com]
+--[www.driversity.com]
+--[www.gbm.net/]
+--[www.ingraham.net]
+--[www.bib-aveiro.rcts.pt]
+--[www.carlos-restaurant.com]
+--[sanbruno.ca.gov/]
+--[www.tufts.edu/as/astronomy/]
+--[www.ci.superior.wi.us/]
+--[www.mitchellrepublic.com]
+--[www.utcc.ac.th/amsar/about/document24.htm]
+--[www.nonchalance.org/]
+--[www.pelicancruiser.com]
+--[www.rayhana.com]



With 179 vs. 7 hits, obviously one must conclude that, at present time at least, having Flash support in the browser matters a great deal more to an average user than having Java support does.

Saturday, August 23, 2008

NetBeans on speed

In the post from yesterday I was a little harsh regarding the performance of recent NetBeans versions, which seems to have taken a toll for the worse. One of the most annoying things is how much scanning and processing is going on all the time which harms responsiveness and the overall user experience. The NetBeans mailing list yielded responses like "more features requires more work" which is fair enough. However, I decided I wanted to have my cake (speed) and eat it too (features).

Memory mapping to the rescue
It's not exactly new. So called "RAM drives" have been used for ages to speed things up, some Linux distro's even mount the temporary files location into RAM to improve performance. We can do the same to our source checkouts to gain massive increase in throughput as well as access time. There are two easy ways to archive this out of the (Linux) box, mount a dedicated RAM drive or use the TMPFS filesystem. Sorry, Windows users will have to look elsewhere for info about how to set up a RAM drive.

RAM drive
In this approach, you allocate a part of the memory and map it to a mount point. The following will create a 64MB RAM disk and let you access it under /media/ramdisk/:

dd if=/dev/zero of=/dev/ram1 bs=4096 count=16k
mke2fs /dev/ram1
mkdir /media/ramdisk
mount /dev/ram1 /media/ramdisk


It turns out that the kernel decides how much memory you can mount like this, on Ubuntu 8.04 the limit is 64MB. You can however change this to your liking by adding a kernel option before it loads:

ramdisk_size= XX


Note however that while you can unmount these again, the physical memory it occupied will not be released to the system. Another limitation of this approach is that whether you need it or not, the RAM disk will permanently occupy whatever you allocated to it - no more and no less. So you should probably only use this approach if you need a small RAM drive or you are just testing the technique out. The only benefit of this approach I can think of, is that your mounted partition will be listed and can be monitored from the system monitor.

Temporary filesystem
TMPFS is rather different than the traditional block oriented RAM disk. It turns out it's actually even easier to deal with, just try this:

mkdir /media/ramdisk
mount tmpfs /media/ramdisk -t tmpfs


Not only is it easier, it's much more flexible. As you no doubt noticed, we did not actually specify any capacity. TMPFS will use half of your RAM as default. Although you can also specify it directly, there's really no need to since TMPFS is not only able to dynamically decrease but also increase its capacity on demand. This means that if you do not use the partition, you won't pay any memory penalty. Apparently TMPFS is even able to use swap file so that IF you should go overboard in space consumption, you won't suffer loss of data or the like.

You can have your system automatically create and mount a TMPFS partition for you, by modifying your /etc/fstab file. Simply add the following to it:

none /media/ramdisk tmpfs defaults,user,size=1G,mode=0777 0 0


Now I don't actually want to run into scenarios where my system has to start using virtual memory, so I limited the size of the temporary partition to 1GB, that leaves me with at least 3GB for the OS and applications.

Applied to a NetBeans project
It's rather hard to measure and express something as subjective as responsiveness and general feeling of an IDE with and without a RAM drive. Suffices to say it feels like it's running at a blazing speed! Nodes expand at once, tabs activate immediately and it's just an overall impressive experience - it feels like you're back to writing C code rather than Java. Let me try and prove this to you the best I can with just static images. The below screen shots both show the result of running the same simple Ant build target on a small NetBeans module project, first on a normal disk drive and then from of a RAM drive.



It took 10 sec to build on my 10.000rpm VelociRaptor disc, the fastest desktop drive available today. An average disc would require around 12-13 sec I suspect.


Running from of the RAM drive it took just 1 sec, an amazing 10x faster. It's the kind of thing you have to see in order to believe.

I also tried placing NetBeans itself as well as its user directory directly on the RAM drive, but without noticeable difference at run time. It did start up 40-50% faster and became responsive right away, but this is probably not an optimization worth going for.


Applied in practice
So there you have it. No magic involved, all you need is enough RAM which is dirt cheap* and some consistent working habits. You do have to manually copy your project onto the RAM drive as well as remember to move it to a non-volatile partition before you shut your computer down. However given the fact that we usually always work out of CVS/SVN/HG this should not be much of an issue, in fact you could claim it encourages healthier check-in habits.

As a cautionary measure though, I have invested in a cheap UPS in case of power loss and when I shut down my system, a script will automatically copy the content of the RAM drive to a persistent backup partition. For convenience, when I start up, another script will automatically copy the most recent backup back onto to the RAM drive. Thus, rather than having to do a full checkout/clone in the morning, I only have to issue an update. If you really wanted to minimize risks, you could also have a timed cron job or a low-priority synchronization deamon running.

In conclusion
Of course, this technique really doesn't have anything to do with NetBeans per se, so any IDE would benefit from it as well as freeform/Ant/Maven projects. If you feel uneasy about working entirely in volatile memory, I suppose it would be possible to simply modify your build scripts such that only the build artifacts are emitted this way, leaving the actual source code intact on a fully journaled non-volatile file system.

I had fun setting this up, though I am somewhat surprised of how I/O-bound NetBeans appears to be. In a related exercise with Visual Studio and C# a few years back I did not experience anywhere near this speed-up, possibly because the compiler and tool chain is multi-threaded unlike javac and Ant.
So go ahead and give it a try, you might like it too. I obviously take no responsibility for whatever data loss you may experience yada yada. You have been warned!

Update:
Someone asked me how to put the automatic copy mechanism I mentioned above into effect. We are going to need 2 scripts as well as installing these at the appropriate life cycle hooks of Linux. In the following I assume you have created a directory at /ramdisk_archives with root access permissions only and that you are using a Debian derived distro.

Put the below script in a file called ramdisk_to_archive.sh, give it execute rights (chmod +x ramdisk_to_archive.sh) and save it under the /etc/init.d/ directory which is usually where service scripts are put.


#!/bin/sh
#/etc/init.d/ramdisk_to_archive.sh
a=$(date +%T-%d-%m-%Y)

mkdir /ramdisk_archives/$a
cp -i -p -R /media/ramdisk/* /ramdisk_archives/$a
rm /ramdisk_archives/latest
ln -s /ramdisk_archives/$a /ramdisk_archives/latest



This script will create a new subdirectory under /ramdisk_archives based on the current timestamp and copy all content of the RAM disk (with file permissions intact), to this newly created folder. Then it will delete any eventual existing link called latest and finally, it will create this new link again and point to the directory we just created. Finally, you need to install it so it runs at system halt (runlevel 0) as well as reboot (runlevel 6) which you can do by typing this:


ln -s /etc/init.d/ramdisk_to_archive.sh /etc/rc0.d/ramdisk_to_archive.sh
ln -s /etc/init.d/ramdisk_to_archive.sh /etc/rc6.d/ramdisk_to_archive.sh



Next time you reboot or shut down your system, the script above will be run, thus saving the content of the RAM disk onto /ramdisk_archives/ somewhere. Now, to have your RAM disk restored again at boot time, write another script:


#!/bin/sh
#/etc/init.d/ramdisk_to_archive.sh
cp -i -p -R /ramdisk_archives/latest/* /media/ramdisk/



Again, remember to give it execute permission and copy it to the services directory like the prior script. This time we're going to hook into all the other remaining runlevels 2-5. To do this, issue the following command:


update-rc.d /etc/init.d/ramdisk_to_archive.sh defaults



That's it. Remember to clean up the /ramdisk_archive folder from time to time as there's no purge or rotation scheme in place. You are of course free to add this yourself, if you do, drop me a line. :)

*You can have 4GB DDR2 for less than US$ 100. Even older consumer motherboards supports 4GB, newer ones 16-32GB.

Wednesday, August 20, 2008

NetBeans: Pretty Much Unusable

NetBeans Pretty Much Unusable
If you care about NetBeans and its performance, an interesting development occurred over the last 24h on the nbusers mailinglist. It seems the upcoming 6.5 release sparked a debate claiming that it's pretty much unusable. While I wouldn't go that far, there is actually some truth to the matter as experienced by my colleagues and I.

A little bit of history
I've been back and forth between various IDE's over the years, coming from Visual Studio (VB/C/C#) I've been rather spoiled in regard to syntax highlighting, code completion and debugging. In fact I remember back in 2001 how I was convinced into doing a college project in Java, primarily because of the assistance offered by Visual Studio J++. At that time, no tool provided these kind of features (although Forte and JBuilder tried).
In this day and age, things looks quite different of course. SUN finally realized that it's worth having great tools (some would argue they have to solve the problems at the tooling level, since the language has effectively gone stale). In any event, todays Java developers can rely on pretty much the same features as Visual Studio. However, for this there still is a price to pay.

The paradox of choice
Having choices is generally a positive thing. But when it comes to software, having too many choices quickly becomes a burden - less is sometimes more. The problem with having multiple IDE's is that, apart from handling of java source files, they aren't really compatible. They all use each their own build system, supports different layout managers etc. so in reality you can't just switch from one IDE to the other. Given the love for committees in the Java space, I do not understand why a project standard was never established through a JSR. In light of this, it becomes rather important you and your project team settle on one IDE that hopefully satisfies everyone.

We chose NetBeans
After initially having used Oracle's JDeveloper for a number of years, we got fed up with the complex stack that Oracle pushed, as well as the semi-yearly releases. We had to live with bugs for a very long time and newer versions of the IDE could never really cope with migrating from the existing code base.
We chose NetBeans 5.5 after having had some success with Swing UI work (Matisse) as well as noticing how SUN were pushing for standards as well as de-facto standards such as Ant etc. Swing now also seemed to be better able to deliver a UI experience where you did not curse constantly at the lack of responsiveness or mismatched L&F. The best part of NetBeans is probably how we constantly get new versions (release, beta, milestone or daily) as well as the plugin community around it. However, not everything is rosy.

Why we might not stay
Since SUN decided to focus their attention on a polyglot of languages, performance seems to be hurting. Java and Swing always felt slow and overly complex to me... but that's subject for another blog post. In NetBeans 6.0 it really did get worse though. Often you would experience lag as you were typing, clicking tab or invoking a menu. It may very well only be 200-300ms but it is enough to be perceived as annoying. I believe it was in 6.1 where they introduced massive Action lazy-loading, with the end result that after NetBeans had started, the whole UI was effectively still unusable for 3-5 seconds.

Having tried 6.5 beta, I can't say I've experienced the gain in speed the new compile-on-save feature promised. Instead, NetBeans feels* even more busy and hence, less responsive to what I am actually trying having it do. I hope this will be sorted out because otherwise I might be compelled to try and move on to Eclipse or IntelliJ. The former always did run fast and smooth but I never really committed to it fully. The latter I have absolutely no idea about, except I keep hearing it's the best.

Is there a point to this?
No not really, apart from documenting the dilemma of choice and warn about the negative developing performance trend of NetBeans. I love NetBeans and the potential I've seen over the last year but productivity comes first. For SUN's sake I hope it will all be resolved, seeing as NetBeans 6.5 is now in beta 2, I doubt it will happen in the 6.5 time frame though.

*I should probably note that I by no means rely on slow hardware (Core II 2.66GHz/4GB Ram/10.000rpm HD) nor outdated software (Ubuntu 8.04 64bit with Sun's Java6u10rc JDK).

Friday, August 15, 2008

JSF and binary content

So I have been learning JSF and JPA at work recently. While I can't say those are the most exciting technologies to me, at least they appear to be (or become) de-facto standards and that means more leverage over the long term (something not always the case when dealing with Java technologies). Using JSF with Facelets can be a little challenging, the tools are only so-so and frankly it bugs me to have to type so much type-unsafe stuff in XML, EL and annotations. Anyway, what this blog entry is about is how you can bridge the gab between JSF page rendering and binary content delivery.

JSF and PDF
Corporate applications often need to deliver a lot of PDF content, and it is no different where I work. Static PDF documents as well as dynamic ones. Even if frames and iframes are generally frowned upon these days, I find then to be the best way to embed PDF documents inside the browser. However, with JSF you can't just make an xhtml page and expect to be able to stream binary content from it, since the HTTP protocol only allows you to send the header once. Indeed, JSF contains assertions to try and catch this and will complain:


java.lang.IllegalStateException: getWriter() has already been called for this response


You can always use an ActionListener or even an accessor on the backing bean if you just want a link to download a PDF, but embedding one inside a frame or iframe is an entirely different matter. I tried many things, most of them felt like fragile hacks. I will now present the best solution in my opinion, it's generic, it's KISS and it solves the problem.

Back to basics: HttpServlet

It turns out that it is possible, inside a processRequest method of a vanilla HttpServlet, to access session scoped backing beans. This means we just need some way of telling the Servlet about where it can uptain the binary data from and various other meta-data. So I came up with the following simple "API" using HTTP GET parameters:


ServletName?BackingBeanName.MethodName[;ContentType][;FileName]


The Servlet then has to do the following:
  1. Parse and validate parameters
  2. Locate the specified backing bean and call the method to uptain the binary content
  3. Stream the result back to the browser using the specified parameters
The complete source code for the Java Servlet that perform this job, you can get here. To use it, you would add it to your web.xml configuration and then simply call it from your JSF or Facelet files like this:



<iframe name ="frame" scrolling="auto" align="center"
src="BinaryJSFServlet?myBackingBean.createPDF;application/pdf" width="100%" height="700" >
</iframe>




This way, you can still keep your state and logic in the backing beans without having to mess around with JavaScript, ActionListeners or PhaseListeners. Just generate your URL in the *.jsp or *.xhtml file such that it specifies a backing bean, a method name to invoke as well as optional MIME type and file name.

Update:
Tim Boudreau mentions a cool technique the code above could make good use of, by relying on a general key/value parser based on dynamic proxies.