javax.ws.rs.core.Link Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2011-2012 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package javax.ws.rs.core;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.ws.rs.Consumes;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.Produces;
import javax.ws.rs.ext.RuntimeDelegate;
import javax.ws.rs.ext.RuntimeDelegate.HeaderDelegate;
import javax.xml.bind.annotation.XmlAnyAttribute;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.namespace.QName;
/**
* Class representing hypermedia links. A hypermedia link may include additional
* parameters beyond its underlying URI. Parameters such as "rel" or "method"
* provide additional meta-data and can be used to easily create instances of
* {@link javax.ws.rs.client.Invocation} in order to follow links.
*
* The methods {@link #toString} and {@link #valueOf} can be used to serialize
* and deserialize a link into a link header (RFC 5988).
*
* @author Marek Potociar
* @author Santiago Pericas-Geertsen
* @see javax.ws.rs.client.Client#invocation
* @since 2.0
*/
public final class Link {
public static final String CONSUMES = "consumes";
public static final String METHOD = "method";
public static final String PRODUCES = "produces";
public static final String TITLE = "title";
public static final String REL = "rel";
public static final String TYPE = "type";
/**
* The underlying link URI.
*/
private URI uri;
/**
* A map for all the link parameters such as "rel", "type", "method", etc.
*/
private MultivaluedMap map = new MultivaluedHashMap();
/**
* Underlying implementation delegate to serialize as link header.
*/
private static final HeaderDelegate delegate =
RuntimeDelegate.getInstance().createHeaderDelegate(Link.class);
/**
* Returns the underlying URI associated with this link.
*
* @return underlying URI
*/
public URI getUri() {
return uri;
}
/**
* Convenience method that returns a {@link javax.ws.rs.core.UriBuilder}
* initialized with this link's underlying URI.
*
* @return UriBuilder initialized using underlying URI
*/
public UriBuilder getUriBuilder() {
return UriBuilder.fromUri(uri);
}
/**
* Returns an immutable list containing all the relation types defined
* on this link via the "rel" parameter. If no relation types are
* defined, this method returns an empty list.
*
* @return list of relation types
*/
public List getRel() {
List l = map.get(REL);
return (l != null) ? new ArrayList(l) : Collections.emptyList();
}
/**
* Returns the value associated with the link "title" param, or
* null if this param is not specified.
*
* @return value of "title" parameter or null
*/
public String getTitle() {
return map.getFirst(TITLE);
}
/**
* Returns the value associated with the link "type" param, or
* null if this param is not specified.
*
* @return value of "type" parameter or null
*/
public String getType() {
return map.getFirst(TYPE);
}
/**
* Returns the value associated with the link "method" param, or
* null if this param is not specified.
*
* @return value of "method" parameter or null
*/
public String getMethod() {
return map.getFirst(METHOD);
}
/**
* Returns an immutable list containing all the types defined on
* this link via the "produces" parameter. If no produces types are
* defined, this method returns an empty list.
*
* @return list of produces types
*/
public List getProduces() {
List l = map.get(PRODUCES);
return (l != null) ? new ArrayList(l) : Collections.emptyList();
}
/**
* Returns an immutable list containing all the types defined on
* this link via the "consumes" parameter. If no consumes types are
* defined, this method returns an empty list.
*
* @return list of consumes types
*/
public List getConsumes() {
List l = map.get(CONSUMES);
return (l != null) ? new ArrayList(l) : Collections.emptyList();
}
/**
* Returns an immutable map that includes all the link parameters
* defined on this link. If defined, this map will include entries
* for "rel", "title" and "type".
*
* @return Immutable map of link parameters
*/
public MultivaluedMap getParams() {
return new MultivaluedHashMap(map);
}
/**
* Equality test for links.
*
* @param other Object to compare against
* @return True if equal, false otherwise
*/
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other instanceof Link) {
final Link olink = (Link) other;
return uri.equals(olink.uri) && map.equals(olink.map);
}
return false;
}
/**
* Hash code computation for links.
*
* @return Hash code for this link
*/
@Override
public int hashCode() {
int hash = 3;
hash = 89 * hash + (this.uri != null ? this.uri.hashCode() : 0);
hash = 89 * hash + (this.map != null ? this.map.hashCode() : 0);
return hash;
}
/**
* Returns a string representation as a link header (RFC 5988).
* All link params are serialized as link-param="value" where value
* is a list of space-separated tokens. For example,
*
* ; title="employee"; rel="manager friend"
*
* @return string link header representation for this link
*/
@Override
public String toString() {
return delegate.toString(this);
}
/**
* Simple parser to convert link header string representations into a link.
*
* link ::= '<' uri '>' (';' link-param)*
* link-param ::= name '=' quoted-string
*
* The resulting language is similar to that defined in RFC 5988.
*
* @param value String representation
* @return newly parsed link
* @throws IllegalArgumentException if a syntax error is found
*/
public static Link valueOf(String value) throws IllegalArgumentException {
return delegate.fromString(value);
}
/**
* Create a new instance initialized from an existing URI.
*
* @param uri a URI that will be used to initialize the Builder.
* @return a new builder
* @throws IllegalArgumentException if uri is null
*/
public static Builder fromUri(URI uri) throws IllegalArgumentException {
Builder b = new Builder();
b.uri(uri);
return b;
}
/**
* Create a new instance initialized from an existing URI.
*
* @param uri a URI that will be used to initialize the Builder.
* @return a new builder
* @throws IllegalArgumentException if uri is null
*/
public static Builder fromUri(String uri) throws IllegalArgumentException {
Builder b = new Builder();
b.uri(uri);
return b;
}
/**
* Create a new instance initialized from another link.
*
* @param link other link used for initialization
* @return a new builder
* @since 2.0
*/
public static Builder fromLink(Link link) {
Builder b = new Builder();
b.uri(link.uri);
b.link.map = new MultivaluedHashMap(link.map);
return b;
}
/**
* Generate a link by introspecting a resource method. This method is a shorthand
* for {@code fromResourceMethod(resource, method, method)}.
*
* @param resource resource class
* @param method name of resource method
* @return link builder to further configure link
* @throws IllegalArgumentException if any argument is null or no method is found
* @see Link#fromResourceMethod(java.lang.Class, java.lang.String, java.lang.String)
*/
public static Builder fromResourceMethod(Class resource, String method)
throws IllegalArgumentException {
return fromResourceMethod(resource, method, method);
}
/**
* Generate a link by introspecting a resource method. Finds the first method
* of a given name and generates a link that includes parameters "method",
* "produces" and "consumes". If "produces" is not defined,
* {@link javax.ws.rs.core.MediaType#WILDCARD} is used. Likewise, if "consumes"
* is not defined, {@link javax.ws.rs.core.MediaType#WILDCARD} is used but
* only when the HTTP method is POST or PUT. The value of "rel" must be specified
* as an argument.
*
* @param resource resource class
* @param method name of resource method
* @param rel value of "rel" parameter
* @return link builder to further configure link
* @throws IllegalArgumentException if any argument is null or no method is found
*/
public static Builder fromResourceMethod(Class resource, String method, String rel)
throws IllegalArgumentException {
if (resource == null || method == null || rel == null) {
throw new IllegalArgumentException("All parameters must be non-null");
}
Builder lb = Link.fromUri(UriBuilder.fromResource(resource).build());
lb.rel(rel);
Method[] methods = resource.getMethods();
for (Method m : methods) {
if (m.getName().equals(method)) {
String httpMethod = null;
for (Annotation a : m.getAnnotations()) {
Class at = a.annotationType();
HttpMethod hm = at.getAnnotation(HttpMethod.class);
if (hm != null) {
httpMethod = at.getSimpleName();
lb.method(httpMethod);
break;
}
}
if (httpMethod == null) {
throw new IllegalArgumentException("Unable to find HTTP method annotation in " + method);
}
Produces ps = m.getAnnotation(Produces.class);
if (ps == null) {
lb.produces(MediaType.WILDCARD);
} else {
for (String p : ps.value()) {
lb.produces(p);
}
}
Consumes cs = m.getAnnotation(Consumes.class);
if (cs == null) {
if (httpMethod.equals(HttpMethod.POST) || httpMethod.equals(HttpMethod.PUT)) {
lb.consumes(MediaType.WILDCARD);
}
} else {
for (String c : cs.value()) {
lb.consumes(c);
}
}
return lb;
}
}
throw new IllegalArgumentException("Method '" + method + "' not found in class '"
+ resource.getName() + "'");
}
/**
* Builder class for hypermedia links.
*
* @author Marek Potociar
* @author Santiago Pericas-Geertsen (Santiago.PericasGeertsen at oracle.com)
* @see Link
* @since 2.0
*/
public static class Builder {
/**
* Link being built by the builder.
*/
private Link link = new Link();
/**
* Underlying builder for link's URI.
*/
private UriBuilder uriBuilder;
/**
* Set underlying URI template for the link being constructed.
*
* @param uri underlying URI for link
* @return the updated builder
* @since 2.0
*/
public Builder uri(URI uri) {
uriBuilder = UriBuilder.fromUri(uri);
return this;
}
/**
* Set underlying string representing URI template for the link being
* constructed.
*
* @param uri underlying URI for link
* @return the updated builder
* @throws IllegalArgumentException if string representation of URI is invalid
* @since 2.0
*/
public Builder uri(String uri) throws IllegalArgumentException {
uriBuilder = UriBuilder.fromUri(uri);
return this;
}
/**
* Convenience method to set a link relation. More than one rel value can
* be specified using this method.
*
* @param name relation name
* @return the updated builder
*/
public Builder rel(String name) {
link.map.add(REL, name);
return this;
}
/**
* Convenience method to set a title on this link. If called more than once,
* the previous value of title is overwritten.
*
* @param title title parameter of this link
* @return the updated builder
*/
public Builder title(String title) {
link.map.putSingle(TITLE, title);
return this;
}
/**
* Convenience method to set a type on this link. More than one
* type value can be specified using this method.
*
* @param type link type as string
* @return the updated builder
*/
public Builder type(String type) {
link.map.add(TYPE, type);
return this;
}
/**
* Convenience method to set a type on this link. If called more than once,
* the previous value of method is overwritten.
*
* @param method HTTP method name
* @return the updated builder
*/
public Builder method(String method) {
link.map.putSingle(METHOD, method);
return this;
}
/**
* Convenience method to set a produces type on this link. More than one
* type value can be specified using this method.
*
* @param type link type as string
* @return the updated builder
*/
public Builder produces(String type) {
link.map.add(PRODUCES, type);
return this;
}
/**
* Convenience method to set a consumes type on this link. More than one
* type value can be specified using this method.
*
* @param type link type as string
* @return the updated builder
*/
public Builder consumes(String type) {
link.map.add(CONSUMES, type);
return this;
}
/**
* Set an arbitrary parameter on this link. This method supports adding
* more than one parameter value for each parameter. It is recommended
* to use the more specific methods {@link #method} or {@link #title}
* when setting these single-valued parameters.
*
* @param name the name of the parameter
* @param value the value set for the parameter
* @return the updated builder
* @throws IllegalArgumentException if either the name or value are null
*/
public Builder param(String name, String value) throws IllegalArgumentException {
if (name == null || value == null) {
throw new IllegalArgumentException("Link parameter name or value is null");
}
link.map.add(name, value);
return this;
}
/**
* Finish building this link and return the instance.
*
* @return newly built link.
*/
public Link build() {
link.uri = uriBuilder.build();
return link;
}
/**
* Finish building this link using the supplied values as URI parameters.
*
* @param values parameters used to build underlying URI
* @return the updated builder
* @throws UriBuilderException maybe thrown when building underlying URI
*/
public Link build(Object... values) throws UriBuilderException {
link.uri = uriBuilder.build(values);
return link;
}
}
/**
* Value type for {@link javax.ws.rs.core.Link} that can be marshalled and
* unmarshalled by JAXB.
*
* @author Santiago Pericas-Geertsen
* @see javax.ws.rs.core.Link.JaxbAdapter
* @since 2.0
*/
public static class JaxbLink {
private URI uri;
private Map params;
public JaxbLink() {
}
public JaxbLink(URI uri) {
this.uri = uri;
}
public JaxbLink(URI uri, Map params) {
this.uri = uri;
this.params = params;
}
@XmlAttribute(name = "href")
public URI getUri() {
return uri;
}
@XmlAnyAttribute
public Map getParams() {
if (params == null) {
params = new HashMap();
}
return params;
}
}
/**
* An implementation of JAXB {@link javax.xml.bind.annotation.adapters.XmlAdapter}
* that maps the JAX-RS {@link javax.ws.rs.core.Link} type to a value that can be
* marshalled and unmarshalled by JAXB.
*
* All link parameters are treated as multi valued except for "title" and "method".
*
* @author Santiago Pericas-Geertsen
* @see javax.ws.rs.core.Link.JaxbLink
* @since 2.0
*/
public static class JaxbAdapter extends XmlAdapter {
@Override
public Link unmarshal(JaxbLink v) throws Exception {
Link.Builder lb = Link.fromUri(v.getUri());
for (Entry e : v.getParams().entrySet()) {
final String name = e.getKey().getLocalPart();
if (TITLE.equals(name) || METHOD.equals(name)) {
lb.param(name, e.getValue().toString());
} else {
String[] values = e.getValue().toString().split(" ");
for (String value : values) {
lb.param(name, value);
}
}
}
return lb.build();
}
@Override
public JaxbLink marshal(Link v) throws Exception {
JaxbLink jl = new JaxbLink(v.getUri());
for (Entry> e : v.getParams().entrySet()) {
final String name = e.getKey();
if (TITLE.equals(name) || METHOD.equals(name)) {
jl.getParams().put(new QName("", name), e.getValue().get(0));
} else {
boolean first = true;
StringBuilder sb = new StringBuilder();
for (String value : e.getValue()) {
if (first) {
first = false;
} else {
sb.append(" ");
}
sb.append(value);
}
jl.getParams().put(new QName("", e.getKey()), sb.toString());
}
}
return jl;
}
}
}