Monday, 10 May 2010

Ubuntu 10.04 : GRUB 2 Issue

I should start by saying that the latest Ubuntu is GREAT. The startup time puts Windows to shame (what exactly is Windows doing for all that time?) and wireless connectivity works flawlessly.

However, I got an unpleasant surprise when I installed it on my Acer laptop. The some reason, GRUB 2 mis-detected my Windows partitions and thought that the hidden recovery partition was the main Windows installation. When I attempted to boot into this partition, I got an unfamiliar Acer screen and, worse still, the Ubuntu partition was completely destroyed! (By completely destroyed I mean that GParted showed the partition as being unallocated.)

After re-installing Ubuntu and reading the GRUB 2 documentation, I came up with the following solution:

1. Disable the os-prober - this is the script that detects the windows partitions and adds them to grub.cfg. You can do this by simply adding the following option to /etc/default/grub:

GRUB_DISABLE_OS_PROBER="true"

2. Put a modified version of the windows menu entries in /etc/grub.d/40_custom - see here for more details.

3. Run update-grub - this recreates the grub.cfg file. In fact, grub.cfg is updated every time there's an update and that's why you should never edit it by hand.

Dual boot restored!

Monday, 12 April 2010

Java XPath

I was pleasantly surprised when I needed to do some XPath in Java recently - it's been a while since I did any and I was expecting some potentially tedious 3rd party integration. Turns out it's now all included in the standard JDK and extracting the data we need from an online resource that looks like this ...








...

Was simply a case of doing this ...
// get connection to items service
URL itemsUrl = new URL(ITEMS_URI);
URLConnection itemsConnection = itemsUrl.openConnection();

// use xpath to extract the item ID's
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
domFactory.setNamespaceAware(true);
DocumentBuilder builder = domFactory.newDocumentBuilder();
Document doc = builder.parse(itemsConnection.getInputStream());
XPath xpath = XPathFactory.newInstance().newXPath();
XPathExpression xpathExpression = xpath.compile("/guides/r/a[@n=\"ItemId\"]/@v");

// create the list of item ID's
List<Long> itemIds = new ArrayList<Long>();
Object result = xpathExpression.evaluate(doc, XPathConstants.NODESET);
NodeList nodes = (NodeList) result;
for (int i = 0; i < nodes.getLength(); i++) {
String itemId = nodes.item(i).getNodeValue();
try {
itemIds.add(Long.parseLong(itemId));
} catch (NumberFormatException e) {
System.out.println("Could not parse item ID: " + itemId);
}
}

return itemIds;
I'm probably being more specific than I need to be there, but I'm not 100% sure what the finished format will look like.

Friday, 12 February 2010

OneToMany Mapping

Sounds trivial, but it recently came to my attention that the OneToMany mappings in our app looked like this ...
@OneToMany(targetEntity = ChildItem.class, mappedBy = "parentId")
@NotFound(action = NotFoundAction.IGNORE)
public List<ChildItem> getChildItems() {
return childItems;
}
This worked (for a while) but it makes the assumption that the foreign key in the 'many' table is mapped to the primary key in the 'one' table. Of course, this caused us no end of grief when we discovered that the parent ID is non-unique and changed the table to include a real ID.

In fact, a more standard approach looks like this:
@OneToMany
@JoinColumn(name = "PARENT_ID", referencedColumnName = "PARENT_ID")
@NotFound(action = NotFoundAction.IGNORE)
public List<ChildItem> getChildItems() {
return childItems;
}
The JoinColumn annotation is basically saying "join these tables by mapping the PARENT_ID in the child items table to PARENT_ID in the parent items table".

Saturday, 21 November 2009

Ajax in Tapestry (Pt 2)

In my last blog entry, I outlined how to take control of Ajax in Tapestry and pass data into our event handler as a request parameter - useful perhaps, but not very exciting!

In this entry I'm going to build on this knowledge to create an insert zone. By default Tapestry will entirely replace the content of a Zone when we update it - what if we want to add more content to it, eg. add more items to a list? Although this feature is supported by Prototype's insert method, Tapestry doesn't expose it - what we need is an insert zone! Let's start with the following TML:

  • ${item}



more items


Here we've bound a unordered list to a Tapestry zone and inside that we're looping through a collection of items. The component class (not shown) is extremely similar to the class described in the previous blog entry - the only real difference is that we're now dealing with a list of items.

Next we need an event handler that looks like this:
@Inject
private Request request;

Object onMoreItemsEvent() {
int pageNumber = Integer.parseInt(request.getParameter("page"));
setCurrentPage(pageNumber); // this pulls in the next page of items
return listZone.getBody();
}

And now we get to the JavaScript. First of all we need an initMoreItems function:
Tapestry.Initializer.initMoreItems = function(element, zoneId, url) {
element = $(element);
$T(element).zoneId = zoneId;
// add a property to the element - we increment this each time the user clicks 'more items'
element.nextPage = 1;
element.observe("click", function(event) {
Event.stop(event);
var zoneObject = Tapestry.findZoneManager(element);
if (!zoneObject) return;
new Ajax.Request(url, {
method: 'get',
parameters: { "page" : element.nextPage },
onException: Tapestry.ajaxFailureHandler,
onFailure: Tapestry.ajaxFailureHandler,
onSuccess : function (transport) {
zoneObject.processReply(transport.responseJSON, true);
element.nextPage++;
}
});
});
}

Again, extremely similar to the previous blog entry, but here we're using a nextPage property to store our page numbers and we're also passing an insert parameter to the processReply function.

At this point we have a working component ... sort of! Problem is, it doesn't actually do what we set out to do, ie. add further content to the div. To get round this, we need to override the Tapestry functions that actually do the update - processReply + show in Tapestry.ZoneManager. We do this by using JavaScript's prototype keyword:
Tapestry.ZoneManager.prototype.processReply = function(reply, insert) {
Tapestry.loadScriptsInReply(reply, function() {
// In a multi-zone update, the reply.content may be blank or missing.
reply.content && this.show(reply.content, insert);
// zones is an object of zone ids and zone content that will be present
// in a multi-zone update response.
Object.keys(reply.zones).each(function (zoneId) {
var manager = Tapestry.findZoneManagerForZone(zoneId);
if (manager) {
var zoneContent = reply.zones[zoneId];
manager.show(zoneContent, insert);
}
});
}.bind(this));
}

Tapestry.ZoneManager.prototype.show = function(content, insert) {
if (insert) {
this.updateElement.insert(content);
}
else {
this.updateElement.update(content);
}
var func = this.element.visible() ? this.updateFunc : this.showFunc;
func.call(this, this.element);
this.element.fire(Tapestry.ZONE_UPDATED_EVENT);
}

Both these functions have been copied from tapestry.js and then had an insert parameter added to them. They've been modified in a way that means they'll continue to work if called without the parameter but will add more content (by calling insert on the Prototype Element) if the insert parameter is supplied. Of course, it's the show function that we're really interested in, but we need to override processReply because that's what we're calling from our onSuccess handler.

Monday, 9 November 2009

Controlling Ajax in Tapestry

The standard way to do Ajax in Tapestry is with Zones, EventLinks and occasionally Blocks. This can result in a very simple TML file:



Hello ${name}!

Hello World!




Hello Sam


And an equally simple Java class:
@InjectComponent
private Zone helloZone;

@Property
private String name;

Object onHelloEvent(String name) {
this.name = name;
return helloZone.getBody();
}

public boolean isNamePopulated() {
return (name != null) && (name.length() > 0);
}

If that's all you need then job done (and not a line of JavaScript in sight!)

However, I'm finding that real world requirements often require greater control over the implementation details - typically this means we need to write our own JavaScript. Here's how we do it ...

First of all we modify the TML file to use a standard anchor in place of the EventLink:

Hello Sam


Now we need to tell JavaScript what to do with that link. But before we can start writing our JavaScript, we add the following to our Java class:
@Environmental
private RenderSupport renderSupport;

@Inject
private ComponentResources resources;

@Property
private String clientId;

void setupRender() {
clientId = renderSupport.allocateClientId(resources);
}

void afterRender() {
Link helloLink = resources.createEventLink("helloEvent", "Sam");
JSONArray parameters = new JSONArray();
parameters.put(clientId);
parameters.put(helloZone.getClientId());
parameters.put(helloLink.toAbsoluteURI());
renderSupport.addInit("initHello", parameters);
}

This makes use of two methods in Tapestry's rendering lifecycle - setupRender + afterRender. In setupRender we're getting unique client ID for this component. In afterRender we're passing some information to a JavaScript function via Tapestry's JSONArray object - note that one of these parameters is the URI of an EventLink we've created. For this to work, we also need to annotate the class:
@IncludeJavaScriptLibrary("myZone.js")

Finally we get to the JavaScript. What we need is a function that gets called when the page loads and accepts the three parameters we're passing in from the Java class. Here's what it looks like:
Tapestry.Initializer.initHello = function(element, zoneId, url) {
element = $(element);
$T(element).zoneId = zoneId;
element.observe("click", function(event) {
Event.stop(event);
var zoneObject = Tapestry.findZoneManager(element);
if (!zoneObject) return;
zoneObject.updateFromURL(url);
});
}

In fact, all that JavaScript was copied from the linkZone function in tapestry.js - we're doing exactly what Tapestry was doing, but now the JavaScript is under our control. :-)

So now we're in control of the JavaScript, what can we do with it? Pretty much anything we like as it turns out, but for now, let's pass the name in as a standard request parameter. First of all we modify our event handler to look like this:
@Inject
private Request request;

Object onHelloEvent() {
this.name = request.getParameter("name");
return helloZone.getBody();
}

And then we replace zoneObject.updateFromURL with a standard Prototype Ajax request:
// zoneObject.updateFromURL(url);
new Ajax.Request(url, {
method: 'get',
parameters: { "name" : "Sam" },
onException: Tapestry.ajaxFailureHandler,
onFailure: Tapestry.ajaxFailureHandler,
onSuccess : function (transport) {
zoneObject.processReply(transport.responseJSON);
}
});

Note that we are now using GET instead of POST and we have also exposed the onSuccess handler. This can prove very useful when we start implementing more advanced requirements.

Saturday, 24 October 2009

Tapestry Revisited

I've been doing a bit of Tapestry 5 at work recently. Have to say, I haven't been much of a fan in the past, but I am starting to see its merits. Yes, it's sometimes quite hard to do relatively simple things, but this is usually down to a lack of documentation more than anything. Once you've arrived at a solution, the end result is often clean and concise - it's getting there that's the issue!

It's very different to working with an MVC framework and requires you to look at server-side development in an entirely different way. For example, developing a functional component nearly always requires some knowledge of JavaScript / Prototype and I seem to frequently find myself looking through the source code for the Tapestry JavaScript library. Although I was uncomfortable with this at first (why would a Java developer need to understand JavaScript?) I'm now finding that I enjoy the challenge. In fact, the weird and wonderful world of JavaScript is turning out to be a more friendly place than I'd feared!

Friday, 25 September 2009

New Mac

Work have kindly given me a brand new MacBook Pro! It features the new 'Unibody Enclosure' and looks absolutely gorgeous - there's a video of it being made here.

Naturally, I spent some of last night making sure my Pygame efforts still worked on it. This pretty much forced me to try Python 2.6 + Pygame 1.9 (I had previously only tried Python 2.5 + Pygame 1.7) so I was quite pleased when it worked first time. Here's what it looks like:


This also hints at a couple of new features - coins + keys! Unfortunately, although you can now collect these items, you can't actually do anything with them yet.