Mittwoch, 10. Juni 2015

Ninja framework: Argument extractors

The last post introduced a simple way to automatically extract a collection of objects from a form and inject it into a controller's action. However, when classes get more complex, this option is not the best one because of two reasons: Method signatures get bloated and the collections have to somehow get attached to the corresponding object (injected into the action as well) by hand. If one writes a new action and uses the built in functionality, it's possible that he forgets to update one of the instance's fields....saves...and boom: the object's data is gone.

The functionality can be gathered into an argument extractor. Sadly, the official documentation only shows an example where the session is used to extract a simple session cookie. But what if you have to get complex form data? Ideally, one wants a clean action method signature, where the instance is injected correctly. This can be done with simply with an annotation:


public Result saveTrip(Context context, @argumentextractors.Trip Trip trip) {

It's important to note, that you can't use other built-in extractors (Param, Params) any more, after you parsed the request. Additionally, your own extractor has to be the first extracting paramter in the signature.

The marker interface specifies the extractor class:

@WithArgumentExtractor(TripExtractor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
public @interface Trip {
}

The magic then has to be implemented by yourself. Therefore, extend a BodyAsExtractorwhere T is the return type you want to extract. There are three methods to be overriden. I don't have a clue what the third one (getFieldName()) does, but the important one is

public Trip extract(Context context) {}

Your form data has to be gathered now. Took me some time to find out how to do this - actually, you can do


String body = "";
while (context.getReader().ready()) {
  body += context.getReader().readLine();
}

and that's it. I was'n able to use the InputStream the context provides directly. Now the ugly part. the params string of the form http://example.org/path/to/file?a=1&b=2&c=3 should result in a list of a=1, b=2, c=3. Since this is a common task, it's implemented in the apache commons htmlUtils - nice wordplay. I extracted some single methods from their library, because I only use a few ones. Now, you have to apply the parsed values by hand. To mention would be, that this can only work, if the keys you use to extract all the stuff don't change between forms. Otherwise, you would have to implement another extractor.


trip.getStops().clear();
for (int x = 2; x < params.size(); x++) {
  trip.getStops().add(params.get(x).getName());
}
return trip;

The nice thing is now, that everyone who uses this object class, can use the extractor and afterwards just has to save the instance regularly in the controller action:
manager.merge(trip);

I'm curious if this is the intended way to extract stuff from forms. It's a pity that such an important requirement isn't documented better.

Keine Kommentare:

Kommentar veröffentlichen