Exposing existing classes for REST top tips

Bolting new functionality onto older systems always has its fair share of fun and games, but exposing internal data structures to external parties is an even greater horror, this does happen though, particularly when clients want to access previously locked down systems via web services or REST interfaces or you want to deliver the same functional via new client eg B2B, mobile etc etc, I have been doing this recently with REST services using the JERSEY library, which like nearly all the good service libraries serialise and deserialise classes for you automatically, however nothing is ever perfect straight out of the box so here are some the things you can do to make your REST services perfect (these work with any JAX/Jackson implementation and often even CXF based systems)

Ill put all the code in first then explain it later (this coded represents the internal class that I am exposing via the services plus the annotations that I have added to it):

package com.ldc.classes;
import java.util.Date;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.codehaus.jackson.annotate.JsonProperty;
public class Address {
    @JsonProperty("referenceId")
    private int Id;
    private Date whenTheyMovedIn;
    private String guessWhenTheyMovedIn;
    private String firstLine;
    private String secondLine;
    private String postCode;
    @JsonSerialize(using = CustomDateSerializer.class)
    public Date getWhenTheyMovedIn() {
        return whenTheyMovedIn;
    }
    public void setWhenTheyMovedIn(Date whenTheyMovedIn) {
        this.whenTheyMovedIn = whenTheyMovedIn;
    }
    @JsonSerialize(using = CustomTextToDateSerializer.class)
    public String getGuessWhenTheyMovedIn() {
        return guessWhenTheyMovedIn;
    }
    public void setGuessWhenTheyMovedIn(String guessWhenTheyMovedIn) {
        this.guessWhenTheyMovedIn = guessWhenTheyMovedIn;
    }
    public String getFirstLine() {
        return firstLine;
    }
    public void setFirstLine(String firstLine) {
        this.firstLine = firstLine;
    }
    public String getSecondLine() {
        return secondLine;
    }
    public void setSecondLine(String secondLine) {
        this.secondLine = secondLine;
    }
    public String getPostCode() {
        return postCode;
    }
    public void setPostCode(String postCode) {
        this.postCode = postCode;
    }
    public int getId() {
        return Id;
    }
    public void setId(int Id) {
        this.Id = Id;
    }
}

package com.ldc.classes;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.SerializerProvider;
public class CustomDateSerializer extends JsonSerializer<Date> {
    @Override
    public void serialize(Date value, JsonGenerator gen, SerializerProvider prov)
            throws IOException, JsonProcessingException {
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz");
        String formattedDate = formatter.format(value);
        gen.writeString(formattedDate);
    }
}

package com.ldc.classes;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.SerializerProvider;
public class CustomTextToDateSerializer extends JsonSerializer<String> {
    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider prov)
            throws IOException, JsonProcessingException {
        Date date = null;
        try {
            date = new SimpleDateFormat("MM/dd/yyyy", Locale.US).parse(value);
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }       
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz");
        String formattedDate = formatter.format(date);
        gen.writeString(formattedDate);
    }
}

Right!, first one is name changes, i.e. you have a field who’s name is just fine in your internal class, but you want to change it when you expose it via REST services (in this case its the “id” field, this was because “id” is a reserved word under iOS which make using it a PITA for the client coders), all you have to do is put the following @annotation and the Alias you want to use just above the field you want to change, and bingo, new name ONLY for REST services

@JsonProperty("referenceId")
    private int Id;

Next is date formatting, the default json date format is not loved by many people I know, and a lot of people prefer ISO 8601, you can use a custom class to do this conversion for you quite easly, again without touching the internal workings of your existing classes, just put this anotation above the ‘getter’ for that field and it will do the conversion for you on the fly (you can see the CustomDateSerializer.class code above)

@JsonSerialize(using = CustomDateSerializer.class)
    public Date getWhenTheyMovedIn() {
        return whenTheyMovedIn;
    }

Finally you can take the custom classes a bit further and change type not just class, for example converting a short form date “DD/MM/YY” stored in a String into a propper Date field. you again just declare a custom class but beef up your code and you can turn anything to anything, funky!

 @JsonSerialize(using = CustomTextToDateSerializer.class)
    public String getGuessWhenTheyMovedIn() {
        return guessWhenTheyMovedIn;
    }

Looking back on this last project and talking with clients about a future one, there is maybe the option of having a dedicated set of classes for external services, far more control and more secure (as, if someone adds a field that really should NOT be exposed it wont automatically flow though to the external service), mind you if you do it that way you will have to weight up the not inconsiderable advantages of speed and easy of maintenance offered by using the internal classes.

but either way these tips might help

Old Comments
————
##### Karsten Lehmann(31/05/2012 15:37:18 GDT)
Nice!
I hope you never try to use this on a Domino server with Extension Library installed. IBM picked Apache Wink as JAX-RS provider and JAX-RS only allows one per JVM (an extremely bad idea).

I did some investigation in this area when we started developing REST APIs for a framework. Somewhere in the JVM, there is a static field that contains the JAX-RS provider and this can only be set one time – with one value.
##### Mark(31/05/2012 18:07:21 GDT)
Karsten: oh poot, hmmmm, there must be a way round that, grrrrrrr

Chris: im off the booze, im on java drugs now
##### chris(31/05/2012 17:46:32 GDT)
dont understand a word of it you must be drinking something stronger than me

Leave a Reply

Your email address will not be published. Required fields are marked *