REST Webservices (JAX-RS)

Introductie

De REST style webservice is i.t.t. de SOAP style een compacte webservice interface. Er wordt niet noodzakelijk gebruik gemaakt van XML structuren voor communicatie van data, maar bijv. JSON. Er wordt gebruik gemaakt van de JAXB standaard voor Java naar XML conversie.

NB: De REST implementatie heeft een deployment descriptor web.xml nodig. Deze dient voor de security afhandeling met de Servlet filter. Omdat de beveiliging van de REST webservices wordt geregeld via een servlet security context waarbij user en password versleuteld in de Header parameter “Authorization” worden meegegeven.

REST Interface specificatie

De REST style kenmerkt zich o.a. doordat deze kan worden aangeroepen via een gewoon HTML Request URL, bijv:


http://localhost:8080/JEE62/emp/list of http://localhost:8080/JEE62/emp/show/1

Het eerste voorbeeld is een aanroep van een REST webservice url voor het tonen van een lijst employees. Als deze in een browser wordt aangeroepen dan zal een xml of JSON gestructureerde lijst als resultaat worden getoond.
Het tweede voorbeeld is een aanroep van een REST webservice url voor het zoeken van een medewerker o.b.v. de employeeId sleutelwaarde “1”. Als deze in een browser wordt aangeroepen dan zal een xml of JSON gestructureerde record van de gevonden employee als resultaat worden getoond.

Behalve parameters die via het pad worden gecodeerd zoals /emp/show/1 kunnen parameters ook op traditionele wijze via de request worden meegegeven. In het volgende voorbeeld wordt een employee list opgevraagd met start bij index 5 en maximaal 10 records.

http://localhost:8080/JEE62/emp/list?start=5&max=10

De REST webservice wordt in JEE6 gedefinieerd met @ annotaties. Aan de hand van het onderstaande voorbeeld wordt dit toegelicht. Als eerste wordt de class gedefinieerd die de webservice implementeert:

@Singleton()
@Path("/emp")
@Produces({MediaType.APPLICATION_XML})
public class EmployeeServiceImpl {
}

In dit geval hebben we een Singleton EJB Enterprise Bean die de service implementeert.
Met de @Path annotatie wordt het url pad gespecificeerd.
Met de @Produces annotatie wordt gespecificeerd welk format wordt geproduceerd, in dit geval XML. Dat kan ook bijv. JSON zijn: MediaType.APPLICATION_JSON.
Vervolgens wordt per method de interface gespecificeerd.

@Path("/list")
@GET
public List<Employee> list(@QueryParam("first") @DefaultValue("0") int first,
                           @QueryParam("max") @DefaultValue("20") int max) {
    }

@Path : definieert het url pad wat een aanvulling is op het pad op class niveau.
@GET : definieert dat het hier om een GET Request gaat.
@QueryParam : definieert in de interface van de method list() de request gebaseerd parameters. Deze worden gemapt op de interne Java parameters van de method interface.

Het betreft hier parameters via het pad conform : ?first=0&max=v20
@DefaultValue : geeft aan welke waarde wordt gebruikt als de parameters niet worden meegegeven. Hiermee wordt een parameter ook als optioneel aangemerkt.

Hieronder het voorbeeld van het interface voor de method show():

@Path("/show/{id}")
@GET
public Employee find(@PathParam("id") int id) {
}

Dit voorbeeld is vergelijkbaar met het voorgaande list() voorbeeld.
Alleen wordt in dit geval gebruik gemaakt van een ander principe om parameters te definieren.

@Path : definieert in het pad tussen { } haakjes welke parameters er zijn. In dit geval 1 parameter “id”.
@PathParam : definieert in de interface de mapping van de REST pad parameter op een interne Java parameter.

REST Implementatie

De implementatie van deze EJB gebaseerde REST webservice maakt gebruik van JPA om de database te benaderen. In onderstaand voorbeeld van de implementatie van de list operatie.

@Path("/list")
@POST
public List<Employee> list(@QueryParam("first") @DefaultValue("0") int first,
                           @QueryParam("max") @DefaultValue("20") int max,
                           @QueryParam("departmentId") @DefaultValue("0") int departmentId)  {
   List<Employee> employees = new ArrayList<Employee>();
   List<Employee> found;
   if (departmentId > 0) {
       found = em.createQuery("select e from Employee e where e.department.departmentId = " + departmentId,Employee.class)
                 .setFirstResult(first).setMaxResults(max).getResultList();
   } else {
       found = em.createQuery("select e from Employee e",Employee.class)
                 .setFirstResult(first).setMaxResults(max).getResultList();
   }
   for (Employee e : found) {
        	employees.add(e.copy());
   }
   return employees;
}

Er worden 2 lists gemaakt van de Employee entity :

  • “found”: hierin wordt de list opgenomen die via JPA uit de database wordt verkregen.
  • “employees” : hierin komt de list die wordt teruggeven

Aan het eind wordt de list “found” uit de database gekopieerd naar de list employees. Dit wordt gedaan om een schone lijst Entity objects te maken. Aan de JPA Entities zelf hangen teveel zaken die je niet mee wilt geven en levert vermoedelijk problemen op.
Om dit te kunnen doen is binnen de JPA entity een copy() method toegevoegd:

	public Employee copy() {
		Employee emp = new Employee();
		emp.setEmployeeId(getEmployeeId());
		emp.setFirstName(getFirstName());
		emp.setLastName(getLastName());
		emp.setBirthDate(getBirthDate());
		emp.setDepartment(getDepartment());
		return emp;
	}

Er wordt gecontroleerd of er parameters worden meegegeven. Afhankelijk daarvan wordt de JPA query opgesteld.

JAXB : Java Architecture for XML Binding

Java Architecture for XML Binding (JAXB) is een Java standaard voor conversie van Java objecten naar XML en terug. JAXB definieert een API voor het lezen en schrijven van Java objecten naar en van XML documenten. Deze standaard is nodig binnen de REST webservices. Ook als er geen gebruik wordt gemaakt van XML, maar bij JSON, is dit toch nodig.

In de voorbeelden worden de Department en Employee JPA Entities voorzien van JAXB annotaties:

@Entity
@XmlRootElement(name="rows")
public class Employee implements Cloneable {

	@XmlAttribute(name="employeeId")
	public int getEmployeeId() {
		return this.employeeId;
	}

	@XmlElement
	public Date getBirthDate() {
		return this.birthDate;
	}
}

@XmlRootElement : definieert het XML root element
@XmlElement : definieert een property als XML element
@XmlAttribute : definieert een property als XML attribute

In dit voorbeeld wordt het root element “rows” genoemd. Dit is nodig voor het gebruik van EasyUI, een jQuery gebaseerd framework. EasyUI wil JSON terugkrijgen met deze naam als root element.

JSON

Gebruik van JSON is met name handig als data naar JavaScript moet worden gecommuniceerd omdat JSON daarin de standaard is voor opslag van object gegevens. Deze kunnen direct aan een object worden toegekend. Maar JSON wordt ook veel op andere platforms gebruikt als lichter alternatief voor XML.

Met @Produces({MediaType.APPLICATION_JSON}) wordt gespecificeerd dat er JSON formaat wordt geretourneerd. De in het voorbeeld aangegeven return van een Listgeeft dus gewoon de structuur van de class als JSON terug.

public List<Employee> list(
   List<Employee> employees = new ArrayList<Employee>();
   return employees;
}

De algemene JSON structuur is:

{
“object”: [
{"attribuut" : "value", "attribuut" : "value",….},
{"attribuut" : "value", "attribuut" : "value",….},
…
]
}

Het begint tussen { } accolade haken met een root object naam met daaronder, tussen rechte [] haken, de records en per record, tussen { } accolade haken, de attributen als “att”:”value” paar, gescheiden met komma’s. Een attribuut kan zelf ook weer een object zijn door als value tussen {} accolade haken een nieuwe lijst attribuut : value paren te definieren :

{
“object”: [
{"attribuut" : {"attr" : "val", "attr" : "val",….} , "attribuut" : "value" , ….},
{"attribuut" : {"attr" : "val", "attr" : "val",….} , "attribuut" : "value" , ….},
…
]
}

Hieronder een voorbeeld van de Employee list in JSON.

{
"employees": [
{
   "@employeeId": "1",
   "birthDate": "2012-10-10T00:00:00+02:00",
   "department": {
      "address": "Kalverstraat 2, Amsterdam",
      "budget": 2000,
   "departmentId": 2,
   "name": "Verkoop"
   },
   "firstName": "Jan",
   "lastName": "Janssen"
   },
   {...
   }, ...]
}

JSON structuren kunnen in diverse omgevingen worden gemaakt en gemanipuleerd met daarvoor beschikbare JSON libraries. Zo heeft Google de populaire Gson library.

Maken van specifieke JSON datastructuren

In dit voorbeeld moet (voor de EasyUI library) een JSON string met alleen maar succes:true worden teruggegeven. Hiervoor wordt de lokale class “ReturnStatus” gemaakt met alleen de boolean property “succes”. (Deze heeft ook de gebruikelijke constructor en getter/setter).

public class ReturnStatus {
    private boolean success;

	public ReturnStatus(boolean success) {
		this.success = success;
	}
	public boolean isSucces() {
		return success;
	}
	public void setSucces(boolean success) {
		this.success = success;
	}
}

De volgende code maakt een instantie van de ResturnStatus class, zet de success property op true via de constructor en vertaalt het object naar JSON met de method “toJson()” van het Gson object.
Dit resultaat met de JSON string wordt vervolgens met return teruggestuurd.

        String res = new Gson().toJson(new ReturnStatus(true));
        return res;

Deze return heeft de volgende JSON structuur :

{
“success”: true
}

Een ander voorbeeld voor EasyUI is het vullen van een combobox lijst. Daarvoor moet een simpel key/value lijstje met department namen worden gemaakt. Hiervoor kunnen de standaard JPA Entities niet worden gebruikt. Daarom wordt de class CB gemaakt met de key/value properties “id” en “name”:

	class CB {
		private int id;
		private String name;

		CB(int id, String name) {
			this.id = id;
			this.name = name;
		}
	}

De volgende code definieert 2 lijsten:

  • found : de list met Departments via de JPA query
  • departments : de list met CB objecten.

De benodigde inhoud voor de departments CB list wordt gekopieerd uit de found Departments list. En de departments list wordt via de “toJson()” method van het Gson object met return terug gegeven.

List<CB> departments = new ArrayList<CB>();
List<Department> found = em.createQuery("select d from Department d",Department.class)
                           .setFirstResult(first).setMaxResults(max).getResultList();

for (Department d : found) {
   departments.add(new CB(d.getDepartmentId(), d.getName()));
}

String res = new Gson().toJson(departments);
return res;

REST testen via de Browser

Het testen van een REST webservice kan eenvoudig met een browser, mits het een GET request type is. Anders kun je voor FireFox een REST test plugin gebruiken.

https://addons.mozilla.org/nl/firefox/addon/restclient/

Met deze client kun je de url invoeren en de request method specificeren zoals hierboven GET. De response header info wordt getoond (bijv status 200 OK) en in de andere tabs kun je ook de verzonden XML of JSON content zien, zoals hieronder getoond.

This entry was posted in JEE, Middleware and tagged , , . Bookmark the permalink.

Geef een reactie

Je e-mailadres wordt niet gepubliceerd.

De volgende HTML tags en attributen zijn toegestaan: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>