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