Index: go =================================================================== --- go (revision 0) +++ go (revision 0) @@ -0,0 +1,16 @@ +echo "start with: mvn compile exec:java " +echo -e "\n\n\n\n\n" + +echo "user 3 /users/3, has links to /payments" +curl --header "Accept:application/xml" http://localhost:9998/users/3 +echo -e "\n\n\n\n\n" +echo "all payments" +curl --header "Accept:application/xml" http://localhost:9998/payments + +echo -e "\n\n\n\n\n" +echo "user 3 payments, /users/3/payments, has links to /users/3" +curl --header "Accept:application/xml" http://localhost:9998/users/3/payments + +echo -e "\n\n\n\n\n" +echo "payments by users, /payments/users/3, has links to /users/3" +curl --header "Accept:application/xml" http://localhost:9998/payments/users/3 Property changes on: go ___________________________________________________________________ Name: svn:special + * Index: src/main/java/com/sun/jersey/samples/helloworld/Main.java =================================================================== --- src/main/java/com/sun/jersey/samples/helloworld/Main.java (revision 1792) +++ src/main/java/com/sun/jersey/samples/helloworld/Main.java (working copy) @@ -55,6 +55,8 @@ initParams.put("com.sun.jersey.config.property.packages", "com.sun.jersey.samples.helloworld.resources"); + initParams.put("com.sun.jersey.spi.container.ContainerResponseFilters", + "com.sun.jersey.samples.helloworld.connectedness.ResourceLinkContentResponseFilter"); System.out.println("Starting grizzly..."); SelectorThread threadSelector = GrizzlyWebContainerFactory.create(BASE_URI, initParams); Index: src/main/java/com/sun/jersey/samples/helloworld/domain/package-info.java =================================================================== --- src/main/java/com/sun/jersey/samples/helloworld/domain/package-info.java (revision 0) +++ src/main/java/com/sun/jersey/samples/helloworld/domain/package-info.java (revision 0) @@ -0,0 +1,12 @@ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlSchema( + namespace = "http://jersey.dev.java.net/jersey-1.0.1/samples/helloworld", + elementFormDefault = XmlNsForm.QUALIFIED, + xmlns = { + @XmlNs(prefix = "xlink", namespaceURI = "http://www.w3.org/1999/xlink") + } +) + +package com.sun.jersey.samples.helloworld.domain; + +import javax.xml.bind.annotation.*; Index: src/main/java/com/sun/jersey/samples/helloworld/domain/ResourceLinkItem.java =================================================================== --- src/main/java/com/sun/jersey/samples/helloworld/domain/ResourceLinkItem.java (revision 0) +++ src/main/java/com/sun/jersey/samples/helloworld/domain/ResourceLinkItem.java (revision 0) @@ -0,0 +1,47 @@ +package com.sun.jersey.samples.helloworld.domain; + +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; + +public class ResourceLinkItem { + @XmlAttribute(namespace="http://www.w3.org/1999/xlink") + private String href; + @XmlAttribute(namespace="http://www.w3.org/1999/xlink") + private String type; + @XmlAttribute + private String rel; + + public ResourceLinkItem(String href, String rel) { + this.href = href; + this.rel = rel; + this.type = "locator"; + } + + public ResourceLinkItem() { + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getRel() { + return rel; + } + + public void setRel(String rel) { + this.rel = rel; + } + +} Index: src/main/java/com/sun/jersey/samples/helloworld/domain/FakeService.java =================================================================== --- src/main/java/com/sun/jersey/samples/helloworld/domain/FakeService.java (revision 0) +++ src/main/java/com/sun/jersey/samples/helloworld/domain/FakeService.java (revision 0) @@ -0,0 +1,57 @@ +package com.sun.jersey.samples.helloworld.domain; + +import java.util.*; + +public class FakeService { + private static Map users = new HashMap(); + private static Map payments = new HashMap(); + static { + users.put("1", new User("1", "Scott Berkun")); + users.put("2", new User("2", "Martin Fowler")); + users.put("3", new User("3", "Scott McCloud")); + + payments.put("1", new Payment("1", "AUD", 123.99, users.get("1"), "1")); + payments.put("2", new Payment("2", "GBP", 273.89, users.get("2"), "2")); + payments.put("3", new Payment("3", "USD", 123.69, users.get("3"), "6")); + payments.put("4", new Payment("4", "EUR", 323.39, users.get("2"), "7")); + payments.put("5", new Payment("5", "AUD", 93.91, users.get("1"), "7")); + } + + public static User getUser(String userId) { + for (User user: users.values()) { + user.getLink().clear(); + } + return users.get(userId); + } + + public static List getPayments() { + clearPaymentLinks(); + return new ArrayList(payments.values()); + } + + public static List getPayments(String userId) { + + List pymnt = new ArrayList(); + if (userId == null) { return new ArrayList(payments.values()); } + for (Map.Entry e: payments.entrySet()) { + Payment p = e.getValue(); + p.getLink().clear(); + if (userId.equals(p.getUser().getUserId())) { + pymnt.add(p); + } + } + return pymnt; + } + + /** + * our fakeservice needs to not persist links as we would expect from a standard service. + */ + private static void clearPaymentLinks() { + for (Payment payment: payments.values()) { + payment.getLink().clear(); + } + + } + + +} Index: src/main/java/com/sun/jersey/samples/helloworld/domain/Order.java =================================================================== --- src/main/java/com/sun/jersey/samples/helloworld/domain/Order.java (revision 0) +++ src/main/java/com/sun/jersey/samples/helloworld/domain/Order.java (revision 0) @@ -0,0 +1,14 @@ +package com.sun.jersey.samples.helloworld.domain; + +import javax.ws.rs.Path; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement +public class Order { + private String id; + private String title; + private String status; + private String userId; + + public Order() {} +} Index: src/main/java/com/sun/jersey/samples/helloworld/domain/Payment.java =================================================================== --- src/main/java/com/sun/jersey/samples/helloworld/domain/Payment.java (revision 0) +++ src/main/java/com/sun/jersey/samples/helloworld/domain/Payment.java (revision 0) @@ -0,0 +1,83 @@ +package com.sun.jersey.samples.helloworld.domain; + +import com.sun.jersey.samples.helloworld.domain.ResourceLinkItem; +import com.sun.jersey.samples.helloworld.connectedness.ResourceLinkContainer; +import com.sun.jersey.samples.helloworld.connectedness.ResourceLink; + +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import java.util.List; +import java.util.ArrayList; + +@XmlRootElement +public class Payment { + private String id; + private String curencyCode; + private Double amount; + + @ResourceLink(href="/users/{userId}") + private User user; + private String orderId; + + @ResourceLinkContainer + @XmlElementWrapper(name="resourceLinks") + private List link = new ArrayList(); + + public Payment() {} + + public Payment(String id, String curencyCode, Double amount, User userId, String orderId) { + this.id = id; + this.curencyCode = curencyCode; + this.amount = amount; + this.user = userId; + this.orderId = orderId; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getCurencyCode() { + return curencyCode; + } + + public void setCurencyCode(String curencyCode) { + this.curencyCode = curencyCode; + } + + public Double getAmount() { + return amount; + } + + public void setAmount(Double amount) { + this.amount = amount; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public String getOrderId() { + return orderId; + } + + public void setOrderId(String orderId) { + this.orderId = orderId; + } + + public List getLink() { + return link; + } + + public void setLink(List link) { + this.link = link; + } +} Index: src/main/java/com/sun/jersey/samples/helloworld/domain/User.java =================================================================== --- src/main/java/com/sun/jersey/samples/helloworld/domain/User.java (revision 0) +++ src/main/java/com/sun/jersey/samples/helloworld/domain/User.java (revision 0) @@ -0,0 +1,102 @@ +package com.sun.jersey.samples.helloworld.domain; + +import com.sun.jersey.samples.helloworld.domain.ResourceLinkItem; +import com.sun.jersey.samples.helloworld.connectedness.ResourceLinkContainer; +import com.sun.jersey.samples.helloworld.connectedness.ResourceLink; + +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import java.util.List; +import java.util.ArrayList; + +@XmlRootElement +public class User { + private String userId; + private String name; + private String email; + private String password; + private List bookmarks; + private List payments = new ArrayList(); + private List comments; + + @ResourceLinkContainer + @XmlElementWrapper(name="resourceLinks") + private List link = new ArrayList(); + + public User() { + + } + + public User(String userId, String name) { + this.userId = userId; + this.name = name; + } + + @ResourceLink(href="/payments/users/{userId}") + public ListgetPayments() { + return payments; + } + // .put("userid", userEntity.getUserid()) +// .put("name", userEntity.getName()) +// .put("email", userEntity.getEmail()) +// .put("password", userEntity.getPassword()) +// .put("bookmarks", uriInfo.getAbsolutePathBuilder().path("bookmarks").build()); +// + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public List getBookmarks() { + return bookmarks; + } + + public void setBookmarks(List bookmarks) { + this.bookmarks = bookmarks; + } + + public List getComments() { + return comments; + } + + public void setComments(List comments) { + this.comments = comments; + } + + public List getLink() { + return link; + } + + public void setLink(List link) { + this.link = link; + } + +} Index: src/main/java/com/sun/jersey/samples/helloworld/resources/PaymentResource.java =================================================================== --- src/main/java/com/sun/jersey/samples/helloworld/resources/PaymentResource.java (revision 0) +++ src/main/java/com/sun/jersey/samples/helloworld/resources/PaymentResource.java (revision 0) @@ -0,0 +1,40 @@ +package com.sun.jersey.samples.helloworld.resources; + +import com.sun.jersey.samples.helloworld.domain.Payment; +import com.sun.jersey.samples.helloworld.domain.FakeService; + +import javax.ws.rs.Path; +import javax.ws.rs.GET; +import javax.ws.rs.Produces; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriInfo; +import java.util.List; + +@Path("payments") +public class PaymentResource { + + private String userId; + private UriInfo uriInfo; + + public PaymentResource(UriInfo uriInfo, String userId) { + this.userId = userId; + this.uriInfo = uriInfo; + } + + @GET + @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) + public List getPayments() { + if (userId != null && userId.length() > 0) { + return FakeService.getPayments(userId); + } + return FakeService.getPayments(); + } + + @GET + @Path("users/{userId}") + @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) + public List getUserPayments(@PathParam("userId") String userId) { + return FakeService.getPayments(userId); + } +} Index: src/main/java/com/sun/jersey/samples/helloworld/resources/UserResource.java =================================================================== --- src/main/java/com/sun/jersey/samples/helloworld/resources/UserResource.java (revision 0) +++ src/main/java/com/sun/jersey/samples/helloworld/resources/UserResource.java (revision 0) @@ -0,0 +1,32 @@ +package com.sun.jersey.samples.helloworld.resources; + +import com.sun.jersey.samples.helloworld.domain.User; +import com.sun.jersey.samples.helloworld.domain.FakeService; + +import javax.ws.rs.Path; +import javax.ws.rs.GET; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.UriInfo; + +@Path("/users") +public class UserResource { + + @GET + @Path("{userId}") + @Produces({MediaType.TEXT_PLAIN, + MediaType.APPLICATION_XML, + MediaType.APPLICATION_JSON}) + public Response getUser(@PathParam("userId") String userId) { + User user = FakeService.getUser(userId); + return user !=null ? Response.ok(user).build() : Response.status(Response.Status.NOT_FOUND).build(); + } + + @Path("{userId}/payments") + public PaymentResource getUserPayments(@Context UriInfo uriInfo, @PathParam("userId") String userId) { + return new PaymentResource(uriInfo, userId); + } +} Index: src/main/java/com/sun/jersey/samples/helloworld/connectedness/ResourceLink.java =================================================================== --- src/main/java/com/sun/jersey/samples/helloworld/connectedness/ResourceLink.java (revision 0) +++ src/main/java/com/sun/jersey/samples/helloworld/connectedness/ResourceLink.java (revision 0) @@ -0,0 +1,15 @@ +package com.sun.jersey.samples.helloworld.connectedness; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.annotation.ElementType; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD}) +public @interface ResourceLink { + String href(); + String title() default ""; + String rel() default ""; + String type() default ""; +} Index: src/main/java/com/sun/jersey/samples/helloworld/connectedness/ResourceLinkContentResponseFilter.java =================================================================== --- src/main/java/com/sun/jersey/samples/helloworld/connectedness/ResourceLinkContentResponseFilter.java (revision 0) +++ src/main/java/com/sun/jersey/samples/helloworld/connectedness/ResourceLinkContentResponseFilter.java (revision 0) @@ -0,0 +1,147 @@ +package com.sun.jersey.samples.helloworld.connectedness; + +import com.sun.jersey.spi.container.ContainerResponseFilter; +import com.sun.jersey.spi.container.ContainerResponse; +import com.sun.jersey.spi.container.ContainerRequest; +import com.sun.jersey.samples.helloworld.domain.ResourceLinkItem; + +import javax.ws.rs.core.UriBuilder; +import java.util.*; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class ResourceLinkContentResponseFilter implements ContainerResponseFilter { + + public ContainerResponse filter(ContainerRequest containerRequest, ContainerResponse containerResponse) { + Object entity = containerResponse.getEntity(); +// if (entity instanceof Response) { +// entity = ((Response)entity).getEntity(); +// } + if (entity != null) { + UriBuilder uriBuilder = containerRequest.getRequestUriBuilder(); + addResourceLinks(entity, uriBuilder); + } + return containerResponse; + } + + private void addResourceLinks(Object entity, UriBuilder uriBuilder) { + if (entity instanceof Collection) { + for (Object obj : ((Collection) entity)) { + addResourceLinks(obj, uriBuilder); + } + + } else { + + Field[] resourceLinkFields = getResourceLinkFields(entity, ResourceLink.class, uriBuilder); + Method[] resourceLinkMethods = getResourceLinkMethods(entity, ResourceLink.class, uriBuilder); + Collection container = getResourceLinksContainer(entity, uriBuilder); + if (container == null) { + return; + } + //todo - handle entity that is an array or continer. + //todo - perform basic evaluation of resourceLink string + //todo - preserve the suffix of the original request if that is an option eg. .es.html, .en.html, .xml, .json + + for (Field field : resourceLinkFields) { + ResourceLink annotation = field.getAnnotation(ResourceLink.class); + resolveAndAddLink(uriBuilder, container, annotation); + } + + for (Method Method : resourceLinkMethods) { + ResourceLink annotation = Method.getAnnotation(ResourceLink.class); + if (annotation != null) { + resolveAndAddLink(uriBuilder, container, annotation); + } + } + } + } + + private void resolveAndAddLink(UriBuilder uriBuilder, Collection container, ResourceLink annotation) { + ResourceLinkItem rli = new ResourceLinkItem(annotation.href(), annotation.rel()); + + //todo - load up map or wrap with something like a beanwrapper to get dynamic properties + Map map = new HashMap(); + map.put("userId", "1"); + map.put("id", "1"); + + UriBuilder tmpBuilder = uriBuilder.clone(); + if (annotation.href().startsWith("/") || annotation.href().startsWith("http://")) { + tmpBuilder.replacePath(annotation.href()); + } else { + tmpBuilder.path(annotation.href()); + } + String href = ""; + try { + href = String.valueOf(tmpBuilder.buildFromMap(map)); + } + catch (NullPointerException e) { + System.err.println("got exception, building uri for " + tmpBuilder.toString()); + } + rli.setHref(href); + + if (container != null) { + container.add(rli); + } + } + + private Collection getResourceLinksContainer(Object entity, UriBuilder uriBuilder) { + Collection container = null; + Field[] resourceContainerFields = getResourceLinkFields(entity, ResourceLinkContainer.class, uriBuilder); + if (resourceContainerFields.length == 0) { + System.err.println("no container list specified."); + return container; + } + + Class containerType = resourceContainerFields[0].getType(); + if (Collection.class.isAssignableFrom(containerType)) { + try { + container = (Collection) resourceContainerFields[0].get(entity); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + return container; + } + + private Field[] getResourceLinkFields(Object entity, Class annotationClass, UriBuilder uriBuilder) { + Class clazz = entity.getClass(); + List results = new ArrayList(); + + for (Field field : clazz.getDeclaredFields()) { + try { + field.setAccessible(true); + //todo - only follow if it is a non-primitive or collection and it will be rendered + if (!field.getType().isPrimitive() && field.getClass().getPackage().getName().startsWith("com.sun.jersey.samples.helloworld.domain")) { + addResourceLinks(field.get(entity), uriBuilder); + } + } catch (IllegalAccessException e) { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + if (field.getAnnotation(annotationClass) != null) { + results.add(field); + } + } + + Field[] arrayResults = new Field[results.size()]; + results.toArray(arrayResults); + return arrayResults; + } + + private Method[] getResourceLinkMethods(Object entity, Class annotationClass, UriBuilder uriBuilder) { + Class clazz = entity.getClass(); + List results = new ArrayList(); + for (Method Method : clazz.getDeclaredMethods()) { + if (Method.getAnnotation(annotationClass) != null) { + results.add(Method); + } + } + if (results.size() == 0) { + return new Method[]{}; + } + Method[] arrayResults = new Method[results.size()]; + results.toArray(arrayResults); + return arrayResults; + } + + +} Index: src/main/java/com/sun/jersey/samples/helloworld/connectedness/ResourceLinkContainer.java =================================================================== --- src/main/java/com/sun/jersey/samples/helloworld/connectedness/ResourceLinkContainer.java (revision 0) +++ src/main/java/com/sun/jersey/samples/helloworld/connectedness/ResourceLinkContainer.java (revision 0) @@ -0,0 +1,13 @@ +package com.sun.jersey.samples.helloworld.connectedness; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.annotation.ElementType; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) + +public @interface ResourceLinkContainer { + String name() default ""; +}