View Javadoc

1   package com.atlassian.plugins.rest.common;
2   
3   import com.google.common.base.Preconditions;
4   import com.google.common.collect.Lists;
5   import org.apache.commons.lang.builder.EqualsBuilder;
6   import org.apache.commons.lang.builder.HashCodeBuilder;
7   
8   import javax.ws.rs.core.CacheControl;
9   import javax.ws.rs.core.MediaType;
10  import javax.ws.rs.core.Request;
11  import javax.ws.rs.core.Response;
12  import javax.ws.rs.core.Variant;
13  import javax.xml.bind.annotation.XmlAttribute;
14  import javax.xml.bind.annotation.XmlElement;
15  import javax.xml.bind.annotation.XmlElementWrapper;
16  import javax.xml.bind.annotation.XmlRootElement;
17  import java.util.Collection;
18  import java.util.Collections;
19  import java.util.List;
20  
21  import static javax.ws.rs.core.MediaType.*;
22  
23  /**
24   * Status entity for those responses that don't have any other entity body.
25   */
26  @XmlRootElement
27  public class Status {
28      /**
29       * This is the plugin that exposes the REST api
30       */
31      @XmlElement
32      private final Plugin plugin;
33  
34      /**
35       * The HTTP reponse code, 200 for ok, 404 for not found, etc.
36       * See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.html">RFC 2616</a> for more information on those response codes.
37       */
38      @XmlElement(name = "status-code")
39      private final Integer code;
40  
41      /**
42       * The plugin specific response code. This is used to differenciate possible error code for example.
43       */
44      @XmlElement(name = "sub-code")
45      private final Integer subCode;
46  
47      /**
48       * A humane readable message for the given status.
49       */
50      @XmlElement
51      private final String message;
52  
53      /**
54       * <p>the eTag.</p>
55       * <p>See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.html">RFC 2616</a> for more information about ETag.
56       */
57      @XmlElement(name = "etag")
58      private final String eTag;
59  
60      /**
61       * Resource that have been updated during this request.
62       */
63      @XmlElementWrapper(name = "resources-created")
64      @XmlElement(name = "link")
65      private final Collection<Link> resourcesCreated;
66  
67      /**
68       * Resource that have been updated during this request.
69       */
70      @XmlElementWrapper(name = "resources-updated")
71      @XmlElement(name = "link")
72      private final Collection<Link> resourcesUpdated;
73  
74      // For JAXB's usage
75      private Status() {
76          this.plugin = null;
77          this.code = -1;
78          this.subCode = -1;
79          this.message = null;
80          this.eTag = null;
81          this.resourcesCreated = null;
82          this.resourcesUpdated = null;
83      }
84  
85      private Status(Plugin plugin, Integer code, Integer subCode, String message, String eTag, Collection<Link> resourcesCreated, Collection<Link> resourcesUpdated) {
86          this.plugin = plugin;
87          this.code = code;
88          this.subCode = subCode;
89          this.message = message;
90          this.eTag = eTag;
91          this.resourcesCreated = resourcesCreated;
92          this.resourcesUpdated = resourcesUpdated;
93      }
94  
95      public static StatusResponseBuilder ok() {
96          return new StatusResponseBuilder(Response.Status.OK);
97      }
98  
99      public static StatusResponseBuilder notFound() {
100         return new StatusResponseBuilder(Response.Status.NOT_FOUND);
101     }
102 
103     public static StatusResponseBuilder error() {
104         // errors are not cached
105         return new StatusResponseBuilder(Response.Status.INTERNAL_SERVER_ERROR).noCache().noStore();
106     }
107 
108     public static StatusResponseBuilder badRequest() {
109         // errors are not cached
110         return new StatusResponseBuilder(Response.Status.BAD_REQUEST).noCache().noStore();
111     }
112 
113     public static StatusResponseBuilder forbidden() {
114         return new StatusResponseBuilder(Response.Status.FORBIDDEN);
115     }
116 
117     public static StatusResponseBuilder unauthorized() {
118         return new StatusResponseBuilder(Response.Status.UNAUTHORIZED);
119     }
120 
121     public static StatusResponseBuilder created(Link link) {
122         return new StatusResponseBuilder(Response.Status.CREATED).created(Preconditions.checkNotNull(link));
123     }
124 
125     public static StatusResponseBuilder conflict() {
126         return new StatusResponseBuilder(Response.Status.CONFLICT);
127     }
128 
129     public Plugin getPlugin() {
130         return plugin;
131     }
132 
133     public int getCode() {
134         return code;
135     }
136 
137     public int getSubCode() {
138         return subCode;
139     }
140 
141     public String getMessage() {
142         return message;
143     }
144 
145     public String getETag() {
146         return eTag;
147     }
148 
149     public Collection<Link> getResourcesCreated() {
150         return Collections.unmodifiableCollection(resourcesCreated);
151     }
152 
153     public Collection<Link> getResourcesUpdated() {
154         return Collections.unmodifiableCollection(resourcesUpdated);
155     }
156 
157     @XmlRootElement
158     public static class Plugin {
159         @XmlAttribute
160         private final String key;
161 
162         @XmlAttribute
163         private final String version;
164 
165         // For JAXB's usage
166         private Plugin() {
167             this.key = null;
168             this.version = null;
169         }
170 
171         public Plugin(String key, String version) {
172             this.key = Preconditions.checkNotNull(key);
173             this.version = Preconditions.checkNotNull(version);
174         }
175 
176         public String getKey() {
177             return key;
178         }
179 
180         public String getVersion() {
181             return version;
182         }
183 
184         @Override
185         public int hashCode() {
186             return new HashCodeBuilder(3, 5).append(key).append(version).toHashCode();
187         }
188 
189         @Override
190         public boolean equals(Object obj) {
191             if (obj == null) {
192                 return false;
193             }
194             if (obj == this) {
195                 return true;
196             }
197             if (obj.getClass() != getClass()) {
198                 return false;
199             }
200             final Plugin plugin = (Plugin) obj;
201             return new EqualsBuilder().append(key, plugin.key).append(version, plugin.version).isEquals();
202         }
203     }
204 
205     public static class StatusResponseBuilder {
206         private final CacheControl cacheControl;
207         private final Response.Status status;
208         private String eTag;
209         private Plugin plugin;
210         private String message;
211         private List<Link> created;
212         private List<Link> updated;
213 
214         private StatusResponseBuilder(Response.Status status) {
215             this(status, new CacheControl());
216         }
217 
218         private StatusResponseBuilder(Response.Status status, CacheControl cacheControl) {
219             this.status = Preconditions.checkNotNull(status);
220             this.cacheControl = Preconditions.checkNotNull(cacheControl);
221         }
222 
223         public StatusResponseBuilder plugin(String key, String version) {
224             plugin = new Plugin(key, version);
225             return this;
226         }
227 
228         public StatusResponseBuilder message(String message) {
229             this.message = message;
230             return this;
231         }
232 
233         public StatusResponseBuilder tag(String eTag) {
234             this.eTag = eTag;
235             return this;
236         }
237 
238         public StatusResponseBuilder noCache() {
239             cacheControl.setNoCache(true);
240             return this;
241         }
242 
243         public StatusResponseBuilder noStore() {
244             cacheControl.setNoStore(true);
245             return this;
246         }
247 
248         public Status build() {
249             return new Status(plugin, status.getStatusCode(), null, message, eTag, created, updated);
250         }
251 
252         public Response response() {
253             return responseBuilder().build();
254         }
255 
256         public Response.ResponseBuilder responseBuilder() {
257             final Response.ResponseBuilder builder =
258                     Response.status(status).cacheControl(cacheControl).tag(eTag).entity(build()).type(APPLICATION_XML);
259 
260             final List<Link> c = getCreated();
261             final List<Link> u = getUpdated();
262             if (c.size() == 1 && u.isEmpty()) {
263                 builder.location(c.get(0).getHref());
264             } else if (u.size() == 1 && c.isEmpty()) {
265                 builder.location(u.get(0).getHref());
266             }
267             return builder;
268         }
269 
270         public StatusResponseBuilder created(final Link link) {
271             getCreated().add(link);
272             return this;
273         }
274 
275         public StatusResponseBuilder updated(final Link link) {
276             getUpdated().add(link);
277             return this;
278         }
279 
280         private List<Link> getCreated() {
281             if (created == null) {
282                 created = Lists.newLinkedList();
283             }
284             return created;
285         }
286 
287         private List<Link> getUpdated() {
288             if (updated == null) {
289                 updated = Lists.newLinkedList();
290             }
291             return updated;
292         }
293     }
294 
295     /**
296      * These are the media types that a Status can be represented as.
297      */
298     private static final List<Variant> POSSIBLE_VARIANTS = Variant.mediaTypes(
299             MediaType.APPLICATION_XML_TYPE,
300             MediaType.APPLICATION_JSON_TYPE).add().build();
301 
302     public static MediaType variantFor(Request request) {
303         Variant v = request.selectVariant(POSSIBLE_VARIANTS);
304         if (v == null) {
305             v = POSSIBLE_VARIANTS.get(0);
306         }
307 
308         return v.getMediaType();
309     }
310 }