package org.etk.core.rest.impl;
import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Variant;
import javax.ws.rs.core.Response.ResponseBuilder;
public final class ResponseImpl extends Response {
/**
* HTTP status.
*/
private final int status;
/**
* Entity. Entity will be written as response message body.
*/
private final Object entity;
/**
* HTTP headers.
*/
private final MultivaluedMap<String, Object> headers;
/**
* Construct Response with supplied status, entity and headers.
*
* @param status HTTP status
* @param entity an entity
* @param headers HTTP headers
*/
ResponseImpl(int status, Object entity, MultivaluedMap<String, Object> headers) {
this.status = status;
this.entity = entity;
this.headers = headers;
}
/**
* {@inheritDoc}
*/
@Override
public Object getEntity() {
return entity;
}
/**
* {@inheritDoc}
*/
@Override
public MultivaluedMap<String, Object> getMetadata() {
return headers;
}
/**
* {@inheritDoc}
*/
@Override
public int getStatus() {
return status;
}
// ResponseBuilder
/**
* @see ResponseBuilder
*/
public static final class ResponseBuilderImpl extends ResponseBuilder {
/**
* HTTP headers which can't be multivalued.
*/
private enum HEADERS {
/**
* Cache control.
*/
CACHE_CONTROL,
/**
* Content-Language.
*/
CONTENT_LANGUAGE,
/**
* Content-Location.
*/
CONTENT_LOCATION,
/**
* Content-Type.
*/
CONTENT_TYPE,
/**
* Content-length.
*/
CONTENT_LENGTH,
/**
* ETag.
*/
ETAG,
/**
* Expires.
*/
EXPIRES,
/**
* Last-Modified.
*/
LAST_MODIFIED,
/**
* Location.
*/
LOCATION
}
/**
* HTTP headers which can't be multivalued.
*/
private static final Map<HEADERS, String> HEADERS_MAP = new HashMap<HEADERS, String>();
static {
HEADERS_MAP.put(HEADERS.CACHE_CONTROL, HttpHeaders.CACHE_CONTROL);
HEADERS_MAP.put(HEADERS.CONTENT_LANGUAGE, HttpHeaders.CONTENT_LANGUAGE);
HEADERS_MAP.put(HEADERS.CONTENT_LOCATION, HttpHeaders.CONTENT_LOCATION);
HEADERS_MAP.put(HEADERS.CONTENT_TYPE, HttpHeaders.CONTENT_TYPE);
HEADERS_MAP.put(HEADERS.CONTENT_LENGTH, HttpHeaders.CONTENT_LENGTH);
HEADERS_MAP.put(HEADERS.ETAG, HttpHeaders.ETAG);
HEADERS_MAP.put(HEADERS.LAST_MODIFIED, HttpHeaders.LAST_MODIFIED);
HEADERS_MAP.put(HEADERS.LOCATION, HttpHeaders.LOCATION);
HEADERS_MAP.put(HEADERS.EXPIRES, HttpHeaders.EXPIRES);
}
/**
* Default HTTP status, No-content, 204.
*/
private static final int DEFAULT_HTTP_STATUS = Response.Status.NO_CONTENT.getStatusCode();
/**
* Default HTTP status.
*/
private int status = DEFAULT_HTTP_STATUS;
/**
* Entity. Entity will be written as response message body.
*/
private Object entity;
/**
* Not multivalued HTTP headers.
*/
private Map<String, Object> headers = new HashMap<String, Object>();
/**
* Multivalued HTTP headers.
*/
private List<Object> headerValues;
/**
* HTTP cookies, Set-Cookie header.
*/
private Map<String, NewCookie> cookies;
/**
* See {@link ResponseBuilder}.
*/
ResponseBuilderImpl() {
}
/**
* Useful for clone method.
* @param other other ResponseBuilderImpl
* @see #clone()
*/
private ResponseBuilderImpl(ResponseBuilderImpl other) {
this.status = other.status;
this.entity = other.entity;
this.headers.putAll(other.headers);
this.cookies.putAll(other.cookies);
this.headerValues.addAll(other.headerValues);
}
/**
* {@inheritDoc}
*/
@Override
public Response build() {
MultivaluedMap<String, Object> m = new OutputHeadersMap();
// following headers can't be multivalued
for (Map.Entry<String, Object> e : headers.entrySet()) {
if (e.getValue() != null)
m.putSingle(e.getKey(), e.getValue());
}
// following headers can be multivalued
if (headerValues != null) {
Iterator<Object> i = headerValues.iterator();
while (i.hasNext())
m.add((String) i.next(), i.next());
}
// add cookies
if (cookies != null) {
for (NewCookie c : cookies.values())
m.add(HttpHeaders.SET_COOKIE, c);
}
Response r = new ResponseImpl(status, entity, m);
reset();
return r;
}
/**
* Set ResponseBuilder to default state.
*/
private void reset() {
status = DEFAULT_HTTP_STATUS;
entity = null;
headers.clear();
headerValues = null;
cookies = null;
}
/**
* {@inheritDoc}
*/
@Override
public ResponseBuilder cacheControl(CacheControl cacheControl) {
headers.put(HEADERS_MAP.get(HEADERS.CACHE_CONTROL), cacheControl);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public ResponseBuilder clone() {
return new ResponseBuilderImpl(this);
}
/**
* {@inheritDoc}
*/
@Override
public ResponseBuilder contentLocation(URI location) {
headers.put(HEADERS_MAP.get(HEADERS.CONTENT_LOCATION), location);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public ResponseBuilder cookie(NewCookie... cookies) {
if (cookies == null && this.cookies != null) {
this.cookies.clear();
} else if (cookies != null) {
if (this.cookies == null)
this.cookies = new HashMap<String, NewCookie>();
// new cookie overwrite old ones with the same name
for (NewCookie c : cookies)
this.cookies.put(c.getName(), c);
}
return this;
}
/**
* {@inheritDoc}
*/
@Override
public ResponseBuilder entity(Object entity) {
this.entity = entity;
return this;
}
/**
* {@inheritDoc}
*/
@Override
public ResponseBuilder expires(Date expires) {
headers.put(HEADERS_MAP.get(HEADERS.EXPIRES), expires);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public ResponseBuilder header(String name, Object value) {
for (Map.Entry<HEADERS, String> e : HEADERS_MAP.entrySet()) {
if (e.getValue().equalsIgnoreCase(name)) {
headers.put(e.getValue(), value);
return this;
}
}
if (value != null)
add(name, value);
else
remove(name);
return this;
}
/**
* Add header with supplied name and Object as header value.
*
* @param name a header name
* @param value a header value
*/
private void add(String name, Object value) {
if (headerValues == null)
headerValues = new ArrayList<Object>();
headerValues.add(name);
headerValues.add(value);
}
/**
* Remove header with supplied name.
* @param name a header name
*/
private void remove(String name) {
if (headerValues == null || headerValues.isEmpty())
return;
Iterator<Object> i = headerValues.iterator();
while (i.hasNext()) {
String n = i.next().toString();
if (n.equalsIgnoreCase(name)) {
i.remove(); // remove name
i.next();
i.remove(); // remove value
} else {
i.next(); // skip next Object in iterator
}
}
}
/**
* {@inheritDoc}
*/
@Override
public ResponseBuilder language(String language) {
headers.put(HEADERS_MAP.get(HEADERS.CONTENT_LANGUAGE), language);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public ResponseBuilder language(Locale language) {
headers.put(HEADERS_MAP.get(HEADERS.CONTENT_LANGUAGE), language);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public ResponseBuilder lastModified(Date lastModified) {
headers.put(HEADERS_MAP.get(HEADERS.LAST_MODIFIED), lastModified);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public ResponseBuilder location(URI location) {
headers.put(HEADERS_MAP.get(HEADERS.LOCATION), location);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public ResponseBuilder status(int status) {
this.status = status;
return this;
}
/**
* {@inheritDoc}
*/
@Override
public ResponseBuilder tag(EntityTag tag) {
headers.put(HEADERS_MAP.get(HEADERS.ETAG), tag);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public ResponseBuilder tag(String tag) {
headers.put(HEADERS_MAP.get(HEADERS.ETAG), tag);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public ResponseBuilder type(MediaType type) {
headers.put(HEADERS_MAP.get(HEADERS.CONTENT_TYPE), type);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public ResponseBuilder type(String type) {
headers.put(HEADERS_MAP.get(HEADERS.CONTENT_TYPE), type);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public ResponseBuilder variant(Variant variant) {
type(variant.getMediaType());
language(variant.getLanguage());
if (variant.getEncoding() != null)
header(HttpHeaders.CONTENT_ENCODING, variant.getEncoding());
return this;
}
/**
* {@inheritDoc}
*/
@Override
public ResponseBuilder variants(List<Variant> variants) {
if (variants.isEmpty())
return this;
boolean acceptMediaType = variants.get(0).getMediaType() != null;
boolean acceptLanguage = variants.get(0).getLanguage() != null;
boolean acceptEncoding = variants.get(0).getEncoding() != null;
for (Variant v : variants) {
acceptMediaType |= v.getMediaType() != null;
acceptLanguage |= v.getLanguage() != null;
acceptEncoding |= v.getEncoding() != null;
}
StringBuffer sb = new StringBuffer();
if (acceptMediaType)
sb.append(HttpHeaders.ACCEPT);
if (acceptLanguage) {
if (sb.length() > 0)
sb.append(',');
sb.append(HttpHeaders.ACCEPT_LANGUAGE);
}
if (acceptEncoding) {
if (sb.length() > 0)
sb.append(',');
sb.append(HttpHeaders.ACCEPT_ENCODING);
}
if (sb.length() > 0)
header(HttpHeaders.VARY, sb.toString());
return this;
}
}
}