<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- We need a doctype to allow us to use special characters like
We use a "strict" DTD to make IE follow the alignment rules. -->
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd">
<body class="container">
<h3>Web Services Versioned</h3>
You might prefer to include an "API version number" in the names of the request and response. Here are 3 examples.<br/>
<div class="eg">
<t:pagelink page="examples/ws/PersonFind01" context="literal:1">PersonFind01/1</t:pagelink><br/>
<t:pagelink page="examples/ws/PersonsFind01">PersonsFind01</t:pagelink><br/>
<t:pagelink page="examples/ws/PersonsFind01" parameters="{'namefilter':'a'}">PersonsFind01?namefilter=a</t:pagelink><br/>
</div>
For a more RESTful implementation try <a href="http://www.tynamo.org/tapestry-resteasy+guide">tapestry-resteasy</a>.<br/><br/>
References:
<a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/corelib/components/PageLink.html">PageLink</a>,
<a href="http://en.wikipedia.org/wiki/Java_Architecture_for_XML_Binding">JAXB</a>,
<a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/StreamResponse.html">StreamResponse</a>,
<a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/services/Response.html">Response</a>,
<a href="http://en.wikipedia.org/wiki/Representational_State_Transfer">REST</a>.<br/><br/>
<t:pagelink page="Index">Home</t:pagelink><br/><br/>
<t:tabgroup>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/ws/WebServicesVersioned.tml"/>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/ws/WebServicesVersioned.java"/>
<t:sourcecodetab src="/web/src/main/resources/META-INF/assets/css/examples/plain.css"/>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/ws/PersonFind01.java"/>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/ws/PersonsFind01.java"/>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/models/examples/ws/Person01.java"/>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/models/examples/ws/PersonFind01Response.java"/>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/models/examples/ws/PersonsFind01Response.java"/>
</t:tabgroup>
</body>
</html>
package jumpstart.web.pages.examples.ws;
import org.apache.tapestry5.annotations.Import;
@Import(stylesheet = "css/examples/plain.css")
public class WebServicesVersioned {
}
.eg {
margin: 20px 0;
padding: 14px;
border: 1px solid #ddd;
border-radius: 6px;
-webkit-border-radius: 6px;
-mox-border-radius: 6px;
}
package jumpstart.web.pages.examples.ws;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.ejb.EJB;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import jumpstart.business.domain.person.Person;
import jumpstart.business.domain.person.iface.IPersonFinderServiceLocal;
import jumpstart.web.models.examples.ws.Person01;
import jumpstart.web.models.examples.ws.PersonFind01Response;
import org.apache.tapestry5.StreamResponse;
import org.apache.tapestry5.services.Response;
public class PersonFind01 {
// Generally useful bits and pieces
@EJB
private IPersonFinderServiceLocal personFinderService;
// The code
StreamResponse onActivate(final Long personId) {
return new StreamResponse() {
private InputStream inputStream;
@Override
public void prepareResponse(Response response) {
PersonFind01Response personFind01Response = findPerson(personId);
if (personFind01Response == null) {
try {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Resource not found");
}
catch (IOException e) {
throw new RuntimeException("Failed to redirect?", e);
}
}
else {
try {
JAXBContext context = JAXBContext.newInstance(personFind01Response.getClass());
Marshaller marshaller = context.createMarshaller();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
marshaller.marshal(personFind01Response, outputStream);
inputStream = new ByteArrayInputStream(outputStream.toByteArray());
// Set content length to prevent chunking - see
// http://tapestry-users.832.n2.nabble.com/Disable-Transfer-Encoding-chunked-from-StreamResponse-td5269662.html#a5269662
response.setHeader("Content-Length", "" + outputStream.size());
}
catch (JAXBException e) {
throw new IllegalStateException(e);
}
}
}
@Override
public String getContentType() {
return "text/xml";
}
@Override
public InputStream getStream() throws IOException {
return inputStream;
}
};
}
public PersonFind01Response findPerson(Long personId) {
PersonFind01Response response = null;
// Ask business layer to find the person
Person person = personFinderService.findPerson(personId);
// If person exists, create a response object
if (person != null) {
Person01 person01 = new Person01(person.getId(), person.getFirstName(), person.getLastName(),
person.getRegion(), person.getStartDate());
response = new PersonFind01Response(person01);
}
return response;
}
}
package jumpstart.web.pages.examples.ws;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import javax.ejb.EJB;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import jumpstart.business.domain.person.Person;
import jumpstart.business.domain.person.iface.IPersonFinderServiceLocal;
import jumpstart.web.models.examples.ws.Person01;
import jumpstart.web.models.examples.ws.PersonsFind01Response;
import org.apache.tapestry5.StreamResponse;
import org.apache.tapestry5.annotations.ActivationRequestParameter;
import org.apache.tapestry5.services.Response;
public class PersonsFind01 {
static private final int MAX_RESULTS = 30;
// Query string parameters
@ActivationRequestParameter(value = "namefilter")
private String partialName;
// Generally useful bits and pieces
@EJB
private IPersonFinderServiceLocal personFinderService;
// The code
StreamResponse onActivate() {
return new StreamResponse() {
private InputStream inputStream;
@Override
public void prepareResponse(Response response) {
PersonsFind01Response persons = null;
if (partialName == null) {
persons = findPersons();
}
else {
persons = findPersons(partialName);
}
try {
JAXBContext context = JAXBContext.newInstance(persons.getClass());
Marshaller marshaller = context.createMarshaller();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
marshaller.marshal(persons, outputStream);
inputStream = new ByteArrayInputStream(outputStream.toByteArray());
// Set content length to prevent chunking - see
// http://tapestry-users.832.n2.nabble.com/Disable-Transfer-Encoding-chunked-from-StreamResponse-td5269662.html#a5269662
response.setHeader("Content-Length", "" + outputStream.size());
}
catch (JAXBException e) {
throw new IllegalStateException(e);
}
}
@Override
public String getContentType() {
return "text/xml";
}
@Override
public InputStream getStream() throws IOException {
return inputStream;
}
};
}
public PersonsFind01Response findPersons() {
// Ask business layer to find the persons
List<Person> persons = personFinderService.findPersons(MAX_RESULTS);
// Create a response object
PersonsFind01Response response = new PersonsFind01Response();
// Populate the response object from the list
for (Person person : persons) {
Person01 person01 = new Person01(person.getId(), person.getFirstName(), person.getLastName(),
person.getRegion(), person.getStartDate());
response.add(person01);
}
return response;
}
public PersonsFind01Response findPersons(String partialName) {
// Ask business layer to find the persons
List<Person> persons = personFinderService.findPersons(partialName, MAX_RESULTS);
// Create a response object
PersonsFind01Response response = new PersonsFind01Response();
// Populate the response object from the list
for (Person person : persons) {
Person01 person01 = new Person01(person.getId(), person.getFirstName(), person.getLastName(),
person.getRegion(), person.getStartDate());
response.add(person01);
}
return response;
}
}
package jumpstart.web.models.examples.ws;
import java.io.Serializable;
import java.util.Date;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import jumpstart.business.domain.person.Regions;
@XmlRootElement(name = "person")
@XmlAccessorType(XmlAccessType.FIELD)
@SuppressWarnings({ "serial", "unused" })
public class Person01 implements Serializable {
@XmlAttribute(name = "id")
private Long id;
private String firstName;
private String lastName;
private Regions region;
private Date startDate;
private Person01() {
}
public Person01(Long id, String firstName, String lastName, Regions region, Date startDate) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.region = region;
this.startDate = startDate;
}
}
package jumpstart.web.models.examples.ws;
import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@SuppressWarnings({ "serial", "unused" })
public class PersonFind01Response implements Serializable {
private Person01 person;
private PersonFind01Response() {
}
public PersonFind01Response(Person01 person) {
this.person = person;
}
}
package jumpstart.web.models.examples.ws;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class PersonsFind01Response {
@XmlElementWrapper(name = "persons")
@XmlElement(name = "person")
private List<Person01> persons = new ArrayList<Person01>();
public PersonsFind01Response() {
}
public void add(Person01 person) {
persons.add(person);
}
}