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 forbidden()
110     {
111         return new StatusResponseBuilder(Response.Status.FORBIDDEN);
112     }
113 
114     public static StatusResponseBuilder unauthorized()
115     {
116         return new StatusResponseBuilder(Response.Status.UNAUTHORIZED);
117     }
118 
119     public static StatusResponseBuilder created(Link link)
120     {
121         return new StatusResponseBuilder(Response.Status.CREATED).created(Preconditions.checkNotNull(link));
122     }
123 
124     public Plugin getPlugin()
125     {
126         return plugin;
127     }
128 
129     public int getCode()
130     {
131         return code;
132     }
133 
134     public int getSubCode()
135     {
136         return subCode;
137     }
138 
139     public String getMessage()
140     {
141         return message;
142     }
143 
144     public String getETag()
145     {
146         return eTag;
147     }
148 
149     public Collection<Link> getResourcesCreated()
150     {
151         return Collections.unmodifiableCollection(resourcesCreated);
152     }
153 
154     public Collection<Link> getResourcesUpdated()
155     {
156         return Collections.unmodifiableCollection(resourcesUpdated);
157     }
158 
159     @XmlRootElement
160     public static class Plugin
161     {
162         @XmlAttribute
163         private final String key;
164 
165         @XmlAttribute
166         private final String version;
167 
168         // For JAXB's usage
169         private Plugin()
170         {
171             this.key = null;
172             this.version = null;
173         }
174 
175         public Plugin(String key, String version)
176         {
177             this.key = Preconditions.checkNotNull(key);
178             this.version = Preconditions.checkNotNull(version);
179         }
180 
181         public String getKey()
182         {
183             return key;
184         }
185 
186         public String getVersion()
187         {
188             return version;
189         }
190 
191         @Override
192         public int hashCode()
193         {
194             return new HashCodeBuilder(3, 5).append(key).append(version).toHashCode();
195         }
196 
197         @Override
198         public boolean equals(Object obj)
199         {
200             if (obj == null)
201             {
202                 return false;
203             }
204             if (obj == this)
205             {
206                 return true;
207             }
208             if (obj.getClass() != getClass())
209             {
210                 return false;
211             }
212             final Plugin plugin = (Plugin) obj;
213             return new EqualsBuilder().append(key, plugin.key).append(version, plugin.version).isEquals();
214         }
215     }
216 
217     public static class StatusResponseBuilder
218     {
219         private final CacheControl cacheControl;
220         private final Response.Status status;
221         private String eTag;
222         private Plugin plugin;
223         private String message;
224         private List<Link> created;
225         private List<Link> updated;
226 
227         private StatusResponseBuilder(Response.Status status)
228         {
229             this(status, new CacheControl());
230         }
231 
232         private StatusResponseBuilder(Response.Status status, CacheControl cacheControl)
233         {
234             this.status = Preconditions.checkNotNull(status);
235             this.cacheControl = Preconditions.checkNotNull(cacheControl);
236         }
237 
238         public StatusResponseBuilder plugin(String key, String version)
239         {
240             plugin = new Plugin(key, version);
241             return this;
242         }
243 
244         public StatusResponseBuilder message(String message)
245         {
246             this.message = message;
247             return this;
248         }
249 
250         public StatusResponseBuilder tag(String eTag)
251         {
252             this.eTag = eTag;
253             return this;
254         }
255 
256         public StatusResponseBuilder noCache()
257         {
258             cacheControl.setNoCache(true);
259             return this;
260         }
261 
262         public StatusResponseBuilder noStore()
263         {
264             cacheControl.setNoStore(true);
265             return this;
266         }
267 
268         public Status build()
269         {
270             return new Status(plugin, status.getStatusCode(), null, message, eTag, created, updated);
271         }
272 
273         public Response response()
274         {
275             return responseBuilder().build();
276         }
277 
278         public Response.ResponseBuilder responseBuilder()
279         {
280             final Response.ResponseBuilder builder = Response.status(status).cacheControl(cacheControl).tag(eTag).entity(build());
281 
282             final List<Link> c = getCreated();
283             final List<Link> u = getUpdated();
284             if (c.size() == 1 && u.isEmpty())
285             {
286                 builder.location(c.get(0).getHref());
287             }
288             else if (u.size() == 1 && c.isEmpty())
289             {
290                 builder.location(u.get(0).getHref());
291             }
292             return builder;
293         }
294 
295         public StatusResponseBuilder created(final Link link)
296         {
297             getCreated().add(link);
298             return this;
299         }
300 
301         public StatusResponseBuilder updated(final Link link)
302         {
303             getUpdated().add(link);
304             return this;
305         }
306 
307         private List<Link> getCreated()
308         {
309             if (created == null)
310             {
311                 created = Lists.newLinkedList();
312             }
313             return created;
314         }
315 
316         private List<Link> getUpdated()
317         {
318             if (updated == null)
319             {
320                 updated = Lists.newLinkedList();
321             }
322             return updated;
323         }
324     }
325 }