I'm not too familiar with link injection, but one easy way to add links is to embed javax.ws.rs.core.Link
s into your JAXB entity classes. It comes with a built in XmlAdapter, Link.JaxbAdapter
that will allow the Link
type to be marshalled and unmarshalled by JAXB. Take for example you have a BookStore
class that holds a collection of Books
. It will also have Link
s from which you can control the navigational cases.
@XmlRootElement(name = "bookstore")
public class BookStore {
private List<Link> links;
private Collection<Book> books;
@XmlElementRef
public Collection<Book> getBooks() {
return books;
}
public void setBooks(Collection<Book> books) {
this.books = books;
}
@XmlElement(name = "link")
@XmlJavaTypeAdapter(Link.JaxbAdapter.class)
public List<Link> getLinks() {
return links;
}
public void setLinks(List<Link> links) {
this.links = links;
}
@XmlTransient
public URI getNext() {
if (links == null) {
return null;
}
for (Link link : links) {
if ("next".equals(link.getRel())) {
return link.getUri();
}
}
return null;
}
@XmlTransient
public URI getPrevious() {
if (links == null) {
return null;
}
for (Link link : links) {
if ("previous".equals(link.getRel())) {
return link.getUri();
}
}
return null;
}
}
The Book
class is just a regular root element JAXB class
@XmlRootElement
public class Book {
@XmlAttribute
private String author;
@XmlAttribute
private String title;
public Book() {}
public Book(String title, String author) {
this.title = title;
this.author = author;
}
}
In the BookResource
class, we can basically add links on demand based on your required logic of what links you want represented. In the example below, there is an in-memory db (this class is used as a stateful singleton class just for example) of books for which I add five books, with incrementing ids. When the request comes in, one or two links will be added to the returning BookStore
. Depending on what id is being requested, we will add a "next" and/or a previous" link. The links will have rel
s that we refer to from our BookStore
class.
@Path("/books")
public class BookResource {
private final Map<Integer, Book> booksDB
= Collections.synchronizedMap(new LinkedHashMap<Integer, Book>());
private final AtomicInteger idCounter = new AtomicInteger();
public BookResource() {
Book book = new Book("Book One", "Author One");
booksDB.put(idCounter.incrementAndGet(), book);
book = new Book("Book Two", "Author Two");
booksDB.put(idCounter.incrementAndGet(), book);
book = new Book("Book Three", "Author Three");
booksDB.put(idCounter.incrementAndGet(), book);
book = new Book("Book Four", "Author Four");
booksDB.put(idCounter.incrementAndGet(), book);
book = new Book("Book Five", "Author Five");
booksDB.put(idCounter.incrementAndGet(), book);
}
@GET
@Formatted
@Path("/{id}")
@Produces(MediaType.APPLICATION_XML)
public BookStore getBook(@Context UriInfo uriInfo, @PathParam("id") int id) {
List<Link> links = new ArrayList<>();
Collection<Book> books = new ArrayList<>();
UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
uriBuilder.path("books");
uriBuilder.path("{id}");
Book book = booksDB.get(id);
if (book == null) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
synchronized(booksDB) {
if (id + 1 <= booksDB.size()) {
int next = id + 1;
URI nextUri = uriBuilder.clone().build(next);
Link link = Link.fromUri(nextUri).rel("next").type(MediaType.APPLICATION_XML).build();
links.add(link);
}
if (id - 1 > 0) {
int previous = id - 1;
URI nextUri = uriBuilder.clone().build(previous);
Link link = Link.fromUri(nextUri).rel("previous").type(MediaType.APPLICATION_XML).build();
links.add(link);
}
}
books.add(book);
BookStore bookStore = new BookStore();
bookStore.setLinks(links);
bookStore.setBooks(books);
return bookStore;
}
}
And in the test case, we request the third book, and we can see there are links for the "next" and "previous" books in our in-memory db. We also call getNext()
on our BookStore
to retrieve the next book in the db, and the result will come with two different links.
public class BookResourceTest {
private static Client client;
@BeforeClass
public static void setUpClass() {
client = ClientBuilder.newClient();
}
@AfterClass
public static void tearDownClass() {
client.close();
}
@Test
public void testBookResourceLinks() throws Exception {
String BASE_URL = "http://localhost:8080/jaxrs-stackoverflow-book/rest/books/3";
WebTarget target = client.target(BASE_URL);
String xmlResult = target.request().accept(MediaType.APPLICATION_XML).get(String.class);
System.out.println(xmlResult);
Unmarshaller unmarshaller = JAXBContext.newInstance(BookStore.class).createUnmarshaller();
BookStore bookStore = (BookStore)unmarshaller.unmarshal(new StringReader(xmlResult));
URI next = bookStore.getNext();
WebTarget nextTarget = client.target(next);
String xmlNextResult = nextTarget.request().accept(MediaType.APPLICATION_XML).get(String.class);
System.out.println(xmlNextResult);
}
}
The result:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<bookstore>
<book author="Author Three" title="Book Three"/>
<link href="http://localhost:8080/jaxrs-stackoverflow-book/rest/books/4" rel="next" type="application/xml"/>
<link href="http://localhost:8080/jaxrs-stackoverflow-book/rest/books/2" rel="previous" type="application/xml"/>
</bookstore>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<bookstore>
<book author="Author Four" title="Book Four"/>
<link href="http://localhost:8080/jaxrs-stackoverflow-book/rest/books/5" rel="next" type="application/xml"/>
<link href="http://localhost:8080/jaxrs-stackoverflow-book/rest/books/3" rel="previous" type="application/xml"/>
</bookstore>
FYI, I'm using Resteasy 3.0.8 with Wildfly 8.1
UPDATE: Using auto-discovery
So I tried out the reference guide example and I can't reproduce your problem. Not sure your complete environment, but here's what I'm using
- Wildfly 8.1
- Resteasy 3.0.8
- Maven
Here's the code
Application class
@ApplicationPath("/rest")
public class BookApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<>();
classes.add(Bookstore.class);
return classes;
}
}
Resource class
@Path("/books")
@Produces({"application/xml", "application/json"})
public class Bookstore {
@AddLinks
@LinkResource(value = Book.class)
@GET
@Formatted
public Collection<Book> getBooks() {
List<Book> books = new ArrayList<>();
books.add(new Book("Book", "Author"));
return books;
}
}
Book class
@XmlRootElement
@XmlAccessorType(XmlAccessType.NONE)
public class Book {
@XmlAttribute
private String author;
@XmlID @XmlAttribute
private String title;
@XmlElementRef
private RESTServiceDiscovery rest;
public Book() {}
public Book(String title, String author) {
this.title = title;
this.author = author;
}
}
pom.xml (Maybe you are missing some dependencies - Note below resteasy-client and resteasy-servlet-initializer was just for testing)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.underdogdevs.web</groupId>
<artifactId>jaxrs-stackoverflow-user</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>jaxrs-stackoverflow-user</name>
<properties>
<endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson2-provider</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxb-provider</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>jaxrs-api</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-links</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-servlet-initializer</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>