Custom querystring parameters in Play

Usually, actions in Play does not receive complex parameters. If you take a look in your project, probably you will see just simple parameters. Integers, Longs etc. Yesterday, trying to find questions to answer in the Play forum, I found one about receiving a java.util.Date parameter in the controller’s action.

    public static Result findByDate(Date date){
    	System.out.println(date);
    	return ok();
    }

When you configure this action in your routes file, an error will be printed in your console.

    No QueryString binder found for type java.util.Date. Try to implement an implicit     QueryStringBindable for this type

By default, Play does not know how to transform a querystring parameter into a Date object. That’s why we have the QueryStringBindable interface. This is like a formatter, but to querystring parameters. The first thing we need to do is to implement our converter.

	public class DateParameter implements QueryStringBindable<DateParameter>{

		private SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");
		private Date value;

		@Override
		public Option<DateParameter> bind(String key,
				Map<String, String[]> parameters) {
			if(parameters.containsKey(key)){
				String date = parameters.get(key)[0];
				try {
					this.value = formatter.parse(date);
				} catch (ParseException e) {
					throw new IllegalArgumentException(e);
				}
				return Option.<DateParameter>Some(this);
			}
			return Option.<DateParameter>None();
		}

		public Date getValue() {
			return value;
		}

		@Override
		public String javascriptUnbind() {			
			return null;
		}

		@Override
		public String unbind(String key) {
			return "key="+formatter.format(value);
		}

	}

Now you need to receive this object as a parameter in your action and change your routes file as well.

    public static Result findByDate(DateParameter date){
    	System.out.println(date.getValue());
    	return ok();
    }

 GET /findByDate	controllers.NewsController.findByDate(date:binders.DateParameter)

A last thing here. In our action, we invoke the getValue method to recover the parsed date. This is why you should keep state in your QueryStringBindable object. Every time a request is made to a method receiving a DateParameter, Play will instantiate this object.

If you have or had this necessity, you can use this interface or the PathBindable, for the cases when the parameter is mixed in the url.

Advertisements

Customizing messages and evolutions in Play, beyond the documentation

Play has a very good forum. Every day a lot of doubts are posted and answered. As a developer who is using Play in almost every project, I follow the forum and I try to help answering as many as questions as I can. Today I bring to the blog two interesting questions that were posted there.

The first question was about how to customize the conversion error in Play. For example, if you have a int field in your class and the user submits a string, for instance, “two”, Play will use a formatter which is responsible for this conversion. If the value is not possible to be converted, a message will be returned. The default message is “Invalid value”.  In order to customize this message you have to override a key defined in Play.

    error.invalid.java.lang.Integer = Value is not a integer

But how to find this key? That is the good part!. It is not explained in the documentation and because of the question, I went to the source code and I found the file messages.default which has all pre defined errors keys in Play.

The second question was about how to run evolutions outside of the web application scope. This one was quite interesting, because I already had this wish and I was not capable to find the solution. Reading the forum, I saw a question about this subject and someone answered with the class OfflineEvolutions. So I gave a try and the code worked like a charm!

   object RunMigrations extends App{

      OfflineEvolutions.applyScript(new File("."),this.getClass().getClassLoader(), "default")
   }

Now, in case you have this necessity, you can run your evolutions in a separated task in sbt, for example. Another case is to run the evolutions every time you need to run a integration test. Again, this is not a case explained in the documentation, but based in a question posted in the forum, I had to find a answer and learned a little more.

Once more, thanks for reading!