/sitebricks/src/main/java/com/google/sitebricks/headless/ReplyMaker.java

http://github.com/dhanji/sitebricks · Java · 239 lines · 184 code · 41 blank · 14 comment · 55 complexity · 625bc77963dad633ba6b75f6142487cd MD5 · raw file

  1. package com.google.sitebricks.headless;
  2. import com.google.common.base.Preconditions;
  3. import com.google.common.collect.Maps;
  4. import com.google.common.io.ByteStreams;
  5. import com.google.inject.Injector;
  6. import com.google.inject.Key;
  7. import com.google.sitebricks.client.Transport;
  8. import com.google.sitebricks.client.transport.Text;
  9. import com.google.sitebricks.rendering.Strings;
  10. import com.google.sitebricks.rendering.Templates;
  11. import javax.servlet.http.HttpServletRequest;
  12. import javax.servlet.http.HttpServletResponse;
  13. import java.io.IOException;
  14. import java.io.InputStream;
  15. import java.util.Map;
  16. /**
  17. * A builder implementation of the Reply interface.
  18. */
  19. class ReplyMaker<E> extends Reply<E> {
  20. // By default, we cool.
  21. int status = HttpServletResponse.SC_OK;
  22. String contentType;
  23. String redirectUri;
  24. Map<String, String> headers = Maps.newHashMap();
  25. Key<? extends Transport> transport = Key.get(Text.class);
  26. E entity;
  27. Class<?> templateKey;
  28. public ReplyMaker(E entity) {
  29. this.entity = entity;
  30. }
  31. @Override
  32. public Reply<E> seeOther(String uri) {
  33. redirectUri = uri;
  34. status = HttpServletResponse.SC_MOVED_PERMANENTLY;
  35. return this;
  36. }
  37. @Override
  38. public Reply<E> seeOther(String uri, int statusCode) {
  39. Preconditions.checkArgument(statusCode >= 300 && statusCode < 400,
  40. "Redirect statuses must be between 300-399");
  41. redirectUri = uri;
  42. status = statusCode;
  43. return this;
  44. }
  45. @Override
  46. public Reply<E> type(String mediaType) {
  47. Strings.nonEmpty(mediaType, "Media type cannot be null or empty");
  48. this.contentType = mediaType;
  49. return this;
  50. }
  51. @Override
  52. public Reply<E> headers(Map<String, String> headers) {
  53. this.headers.putAll(headers);
  54. return this;
  55. }
  56. @Override
  57. public Reply<E> notFound() {
  58. status = HttpServletResponse.SC_NOT_FOUND;
  59. return this;
  60. }
  61. @Override
  62. public Reply<E> unauthorized() {
  63. status = HttpServletResponse.SC_UNAUTHORIZED;
  64. return this;
  65. }
  66. @Override
  67. public Reply<E> as(Key<? extends Transport> transport) {
  68. Preconditions.checkArgument(null != transport, "Transport class cannot be null!");
  69. this.transport = transport;
  70. return this;
  71. }
  72. @Override
  73. public Reply<E> as(Class<? extends Transport> transport) {
  74. Preconditions.checkArgument(null != transport, "Transport class cannot be null!");
  75. this.transport = Key.get(transport);
  76. return this;
  77. }
  78. @Override
  79. public Reply<E> redirect(String url) {
  80. Strings.nonEmpty(url, "Redirect URL must be non empty!");
  81. this.redirectUri = url;
  82. status = HttpServletResponse.SC_MOVED_TEMPORARILY;
  83. return this;
  84. }
  85. @Override
  86. public Reply<E> forbidden() {
  87. status = HttpServletResponse.SC_FORBIDDEN;
  88. return this;
  89. }
  90. @Override
  91. public Reply<E> noContent() {
  92. status = HttpServletResponse.SC_NO_CONTENT;
  93. return this;
  94. }
  95. @Override
  96. public Reply<E> error() {
  97. status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
  98. return this;
  99. }
  100. @Override
  101. public Reply<E> badRequest() {
  102. status = HttpServletResponse.SC_BAD_REQUEST;
  103. return this;
  104. }
  105. @Override
  106. public Reply<E> status(int code) {
  107. status = code;
  108. return this;
  109. }
  110. @Override
  111. public Reply<E> ok() {
  112. status = HttpServletResponse.SC_OK;
  113. return this;
  114. }
  115. @Override
  116. public Reply<E> template(Class<?> templateKey) {
  117. this.templateKey = templateKey;
  118. return this;
  119. }
  120. @Override @SuppressWarnings("unchecked")
  121. void populate(Injector injector, HttpServletResponse response) throws IOException {
  122. // If we should not bother with the chain
  123. if (Reply.NO_REPLY == this) {
  124. injector.getInstance(HttpServletRequest.class).setAttribute(Reply.NO_REPLY_ATTR, Boolean.TRUE);
  125. return;
  126. }
  127. // This is where we take all the builder values and encode them in the response.
  128. Transport transport = injector.getInstance(this.transport);
  129. // Set any headers (we do this first, so we can override any cheekily set headers).
  130. if (!headers.isEmpty()) {
  131. for (Map.Entry<String, String> header : headers.entrySet()) {
  132. response.setHeader(header.getKey(), header.getValue());
  133. }
  134. }
  135. // If the content type was already set, do nothing.
  136. if (response.getContentType() == null) {
  137. // By default we use the content type of the transport.
  138. if (null == contentType) {
  139. response.setContentType(transport.contentType());
  140. } else {
  141. response.setContentType(contentType);
  142. }
  143. }
  144. // Send redirect
  145. if (null != redirectUri) {
  146. response.sendRedirect(redirectUri);
  147. response.setStatus(status); // HACK to override whatever status the redirect sets.
  148. return;
  149. }
  150. // Write out data.
  151. response.setStatus(status);
  152. if (null != templateKey) {
  153. response.getWriter().write(injector.getInstance(Templates.class).render(templateKey, entity));
  154. } else if (null != entity) {
  155. if (entity instanceof InputStream) {
  156. // Stream the response rather than marshalling it through a transport.
  157. InputStream inputStream = (InputStream) entity;
  158. try {
  159. ByteStreams.copy(inputStream, response.getOutputStream());
  160. } finally {
  161. inputStream.close();
  162. }
  163. } else {
  164. // TODO(dhanji): This feels wrong to me. We need a better way to obtain the entity type.
  165. transport.out(response.getOutputStream(), (Class<E>) entity.getClass(), entity);
  166. }
  167. }
  168. }
  169. @Override
  170. public boolean equals(Object other) {
  171. if(!(other instanceof ReplyMaker<?>))
  172. return false;
  173. @SuppressWarnings("unchecked")
  174. ReplyMaker<E> o = (ReplyMaker<E>)other;
  175. if(this.status != o.status)
  176. return false;
  177. if((this.contentType != o.contentType)
  178. && (this.contentType != null && !this.contentType.equals(o.contentType))
  179. && (this.contentType == null && o.contentType != null))
  180. return false;
  181. if((this.redirectUri != o.redirectUri)
  182. && (this.redirectUri != null && !this.redirectUri.equals(o.redirectUri))
  183. && (this.redirectUri == null && o.redirectUri != null))
  184. return false;
  185. if(!this.headers.equals(o.headers))
  186. return false;
  187. if(!this.transport.equals(o.transport))
  188. return false;
  189. if(this.templateKey != o.templateKey)
  190. return false;
  191. if((this.entity != o.entity)
  192. && (this.entity != null && !this.entity.equals(o.entity))
  193. && (this.entity == null && o.entity != null))
  194. return false;
  195. // All tests passed, the objects must be equal
  196. return true;
  197. }
  198. }