package com.google.sitebricks.headless;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.google.common.io.ByteStreams;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.sitebricks.client.Transport;
import com.google.sitebricks.client.transport.Text;
import com.google.sitebricks.rendering.Strings;
import com.google.sitebricks.rendering.Templates;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
/**
* A builder implementation of the Reply interface.
*/
class ReplyMaker<E> extends Reply<E> {
// By default, we cool.
int status = HttpServletResponse.SC_OK;
String contentType;
String redirectUri;
Map<String, String> headers = Maps.newHashMap();
Key<? extends Transport> transport = Key.get(Text.class);
E entity;
Class<?> templateKey;
public ReplyMaker(E entity) {
this.entity = entity;
}
@Override
public Reply<E> seeOther(String uri) {
redirectUri = uri;
status = HttpServletResponse.SC_MOVED_PERMANENTLY;
return this;
}
@Override
public Reply<E> seeOther(String uri, int statusCode) {
Preconditions.checkArgument(statusCode >= 300 && statusCode < 400,
"Redirect statuses must be between 300-399");
redirectUri = uri;
status = statusCode;
return this;
}
@Override
public Reply<E> type(String mediaType) {
Strings.nonEmpty(mediaType, "Media type cannot be null or empty");
this.contentType = mediaType;
return this;
}
@Override
public Reply<E> headers(Map<String, String> headers) {
this.headers.putAll(headers);
return this;
}
@Override
public Reply<E> notFound() {
status = HttpServletResponse.SC_NOT_FOUND;
return this;
}
@Override
public Reply<E> unauthorized() {
status = HttpServletResponse.SC_UNAUTHORIZED;
return this;
}
@Override
public Reply<E> as(Key<? extends Transport> transport) {
Preconditions.checkArgument(null != transport, "Transport class cannot be null!");
this.transport = transport;
return this;
}
@Override
public Reply<E> as(Class<? extends Transport> transport) {
Preconditions.checkArgument(null != transport, "Transport class cannot be null!");
this.transport = Key.get(transport);
return this;
}
@Override
public Reply<E> redirect(String url) {
Strings.nonEmpty(url, "Redirect URL must be non empty!");
this.redirectUri = url;
status = HttpServletResponse.SC_MOVED_TEMPORARILY;
return this;
}
@Override
public Reply<E> forbidden() {
status = HttpServletResponse.SC_FORBIDDEN;
return this;
}
@Override
public Reply<E> noContent() {
status = HttpServletResponse.SC_NO_CONTENT;
return this;
}
@Override
public Reply<E> error() {
status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
return this;
}
@Override
public Reply<E> badRequest() {
status = HttpServletResponse.SC_BAD_REQUEST;
return this;
}
@Override
public Reply<E> status(int code) {
status = code;
return this;
}
@Override
public Reply<E> ok() {
status = HttpServletResponse.SC_OK;
return this;
}
@Override
public Reply<E> template(Class<?> templateKey) {
this.templateKey = templateKey;
return this;
}
@Override @SuppressWarnings("unchecked")
void populate(Injector injector, HttpServletResponse response) throws IOException {
// If we should not bother with the chain
if (Reply.NO_REPLY == this) {
injector.getInstance(HttpServletRequest.class).setAttribute(Reply.NO_REPLY_ATTR, Boolean.TRUE);
return;
}
// This is where we take all the builder values and encode them in the response.
Transport transport = injector.getInstance(this.transport);
// Set any headers (we do this first, so we can override any cheekily set headers).
if (!headers.isEmpty()) {
for (Map.Entry<String, String> header : headers.entrySet()) {
response.setHeader(header.getKey(), header.getValue());
}
}
// If the content type was already set, do nothing.
if (response.getContentType() == null) {
// By default we use the content type of the transport.
if (null == contentType) {
response.setContentType(transport.contentType());
} else {
response.setContentType(contentType);
}
}
// Send redirect
if (null != redirectUri) {
response.sendRedirect(redirectUri);
response.setStatus(status); // HACK to override whatever status the redirect sets.
return;
}
// Write out data.
response.setStatus(status);
if (null != templateKey) {
response.getWriter().write(injector.getInstance(Templates.class).render(templateKey, entity));
} else if (null != entity) {
if (entity instanceof InputStream) {
// Stream the response rather than marshalling it through a transport.
InputStream inputStream = (InputStream) entity;
try {
ByteStreams.copy(inputStream, response.getOutputStream());
} finally {
inputStream.close();
}
} else {
// TODO(dhanji): This feels wrong to me. We need a better way to obtain the entity type.
transport.out(response.getOutputStream(), (Class<E>) entity.getClass(), entity);
}
}
}
@Override
public boolean equals(Object other) {
if(!(other instanceof ReplyMaker<?>))
return false;
@SuppressWarnings("unchecked")
ReplyMaker<E> o = (ReplyMaker<E>)other;
if(this.status != o.status)
return false;
if((this.contentType != o.contentType)
&& (this.contentType != null && !this.contentType.equals(o.contentType))
&& (this.contentType == null && o.contentType != null))
return false;
if((this.redirectUri != o.redirectUri)
&& (this.redirectUri != null && !this.redirectUri.equals(o.redirectUri))
&& (this.redirectUri == null && o.redirectUri != null))
return false;
if(!this.headers.equals(o.headers))
return false;
if(!this.transport.equals(o.transport))
return false;
if(this.templateKey != o.templateKey)
return false;
if((this.entity != o.entity)
&& (this.entity != null && !this.entity.equals(o.entity))
&& (this.entity == null && o.entity != null))
return false;
// All tests passed, the objects must be equal
return true;
}
}