package org.handwerkszeug.riak.transport.rest.internal;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.codehaus.jackson.map.ObjectMapper;
import org.handwerkszeug.riak.Markers;
import org.handwerkszeug.riak.RiakException;
import org.handwerkszeug.riak.model.Bucket;
import org.handwerkszeug.riak.model.DefaultRiakObject;
import org.handwerkszeug.riak.model.GetOptions;
import org.handwerkszeug.riak.model.Link;
import org.handwerkszeug.riak.model.Location;
import org.handwerkszeug.riak.model.PostOptions;
import org.handwerkszeug.riak.model.PutOptions;
import org.handwerkszeug.riak.model.Quorum;
import org.handwerkszeug.riak.model.RiakObject;
import org.handwerkszeug.riak.model.StoreOptions;
import org.handwerkszeug.riak.nls.Messages;
import org.handwerkszeug.riak.transport.rest.InputStreamHandler;
import org.handwerkszeug.riak.transport.rest.LinkCondition;
import org.handwerkszeug.riak.transport.rest.RestRiakConfig;
import org.handwerkszeug.riak.transport.rest.RiakHttpHeaders;
import org.handwerkszeug.riak.util.HttpUtil;
import org.handwerkszeug.riak.util.StringUtil;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBufferOutputStream;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.handler.codec.http.DefaultHttpRequest;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpMessage;
import org.jboss.netty.handler.codec.http.HttpMethod;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpVersion;
import org.jboss.netty.handler.codec.http.QueryStringEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author taichi
*/
public class RequestFactory {
static final Logger LOG = LoggerFactory.getLogger(RequestFactory.class);
RestRiakConfig config;
String host;
String clientId;
ObjectMapper objectMapper = new ObjectMapper();
public RequestFactory(String host, RestRiakConfig config) {
super();
this.host = removeSlashIfNeed(host);
this.config = config;
}
protected String removeSlashIfNeed(String uri) {
return uri.endsWith("/") ? uri.substring(0, uri.length() - 1) : uri;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientId() {
return this.clientId;
}
public HttpRequest newRequest(String path, HttpMethod method) {
return newRequest(this.host + "/" + this.config.getRawName(), path,
method);
}
public HttpRequest newDeleteRequest(Location location) {
HttpRequest request = newRequest("/" + location.getBucket() + "/"
+ location.getKey(), HttpMethod.DELETE);
return request;
}
public HttpRequest newGetBucketRequest(String bucket) {
HttpRequest request = newRequest("/" + bucket + "?props=true",
HttpMethod.GET);
return request;
}
public HttpRequest newGetRequst(Location location) {
HttpRequest request = newRequest("/" + location.getBucket() + "/"
+ location.getKey(), HttpMethod.GET);
return request;
}
public HttpRequest newGetRequst(Location location, GetOptions options) {
HttpRequest request = newGetRequst(location);
QueryStringEncoder params = new QueryStringEncoder(request.getUri());
if (options.getReadQuorum() != null) {
params.addParam("r", options.getReadQuorum().getString());
}
// TODO PR support.
if (StringUtil.isEmpty(options.getIfNoneMatch()) == false) {
request.setHeader(HttpHeaders.Names.IF_NONE_MATCH,
options.getIfNoneMatch());
}
if (StringUtil.isEmpty(options.getIfMatch()) == false) {
request.setHeader(HttpHeaders.Names.IF_MATCH, options.getIfMatch());
}
if (options.getIfModifiedSince() != null) {
request.setHeader(HttpHeaders.Names.IF_MODIFIED_SINCE,
HttpUtil.format(options.getIfModifiedSince()));
}
request.setUri(params.toString());
return request;
}
public HttpRequest newListBucketsRequest() {
HttpRequest request = newRequest("?buckets=true", HttpMethod.GET);
return request;
}
public HttpRequest newListKeysRequest(String bucket) {
HttpRequest request = newRequest("/" + bucket
+ "?props=false&keys=stream", HttpMethod.GET);
return request;
}
public HttpRequest newPingRequest() {
HttpRequest request = newRequest("/ping", HttpMethod.GET);
return request;
}
public HttpRequest newGetStatsRequest() {
HttpRequest request = newRequest(this.host, "/stats", HttpMethod.GET);
return request;
}
public HttpRequest newPostRequest(RiakObject<byte[]> content) {
HttpRequest request = newRequest("/"
+ content.getLocation().getBucket(), HttpMethod.POST);
merge(request, content);
return request;
}
public HttpRequest newPutRequest(RiakObject<byte[]> content) {
Location location = content.getLocation();
HttpRequest request = newRequest("/" + location.getBucket() + "/"
+ location.getKey(), HttpMethod.PUT);
merge(request, content);
return request;
}
public HttpRequest newPutRequest(RiakObject<byte[]> content,
PutOptions options) {
HttpRequest request = newPutRequest(content);
if (StringUtil.isEmpty(options.getIfNoneMatch()) == false) {
request.addHeader(HttpHeaders.Names.IF_NONE_MATCH,
options.getIfNoneMatch());
}
if (StringUtil.isEmpty(options.getIfMatch()) == false) {
request.addHeader(HttpHeaders.Names.IF_MATCH, options.getIfMatch());
}
if (options.getIfModifiedSince() != null) {
request.addHeader(HttpHeaders.Names.IF_MODIFIED_SINCE,
HttpUtil.format(options.getIfModifiedSince()));
}
if (options.getIfUnmodifiedSince() != null) {
request.addHeader(HttpHeaders.Names.IF_UNMODIFIED_SINCE,
HttpUtil.format(options.getIfUnmodifiedSince()));
}
QueryStringEncoder params = to(options, request);
request.setUri(params.toString());
return request;
}
public HttpRequest newPostRequest(RiakObject<byte[]> content,
PostOptions options) {
HttpRequest request = newPostRequest(content);
QueryStringEncoder params = to(options, request);
request.setUri(params.toString());
return request;
}
protected QueryStringEncoder to(StoreOptions options, HttpRequest request) {
QueryStringEncoder params = new QueryStringEncoder(request.getUri());
if (options.getWriteQuorum() != null) {
params.addParam("w", options.getWriteQuorum().getString());
}
if (options.getDurableWriteQuorum() != null) {
params.addParam("dw", options.getDurableWriteQuorum().getString());
}
if (options.getReturnBody()) {
params.addParam("returnbody",
String.valueOf(options.getReturnBody()));
}
return params;
}
public HttpRequest newDeleteRequest(Location location, Quorum readWrite) {
HttpRequest request = newDeleteRequest(location);
QueryStringEncoder params = new QueryStringEncoder(request.getUri());
params.addParam("rw", readWrite.getString());
request.setUri(params.toString());
return request;
}
public HttpRequest newSetBucketRequest(Bucket bucket) {
try {
BucketHolder holder = new BucketHolder();
holder.props = bucket;
ChannelBuffer buffer = ChannelBuffers.dynamicBuffer();
OutputStream out = new ChannelBufferOutputStream(buffer);
this.objectMapper.writeValue(out, holder);
HttpRequest request = newRequest("/" + bucket.getName(),
HttpMethod.PUT);
request.setHeader(HttpHeaders.Names.CONTENT_LENGTH,
buffer.readableBytes());
request.setHeader(HttpHeaders.Names.CONTENT_TYPE,
RiakHttpHeaders.CONTENT_JSON);
request.setHeader(HttpHeaders.Names.ACCEPT,
RiakHttpHeaders.CONTENT_JSON);
request.setContent(buffer);
return request;
} catch (IOException e) {
throw new RiakException(e);
}
}
public HttpRequest newMapReduceRequest() {
HttpRequest request = newRequest(this.host,
"/" + this.config.getMapReduceName() + "?chunked=true",
HttpMethod.POST);
request.setHeader(HttpHeaders.Names.CONTENT_TYPE,
RiakHttpHeaders.CONTENT_JSON);
return request;
}
public HttpRequest newWalkRequst(Location walkbegin,
List<LinkCondition> conditions) {
StringBuilder stb = new StringBuilder(64);
stb.append('/');
stb.append(walkbegin.getBucket());
stb.append('/');
stb.append(walkbegin.getKey());
for (LinkCondition cond : conditions) {
stb.append('/');
stb.append(cond.getBucket());
stb.append(',');
stb.append(cond.getTag());
stb.append(',');
if (cond.getKeep()) {
stb.append('1');
} else {
stb.append('_');
}
}
return newRequest(stb.toString(), HttpMethod.GET);
}
public HttpRequest newRequest(String app, String path, HttpMethod method) {
try {
URI uri = new URI(app + path);
LOG.debug(Markers.BOUNDARY, uri.toASCIIString());
HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1,
method, uri.toASCIIString());
request.setHeader(HttpHeaders.Names.HOST, uri.getHost());
request.setHeader(HttpHeaders.Names.CONNECTION,
HttpHeaders.Values.KEEP_ALIVE);
if (StringUtil.isEmpty(this.clientId) == false) {
request.setHeader(RiakHttpHeaders.CLIENT_ID, this.clientId);
}
return request;
} catch (URISyntaxException e) {
throw new RiakException(e);
}
}
public void merge(HttpRequest request, RiakObject<byte[]> content) {
if (StringUtil.isEmpty(content.getVectorClock()) == false) {
request.setHeader(RiakHttpHeaders.VECTOR_CLOCK,
content.getVectorClock());
}
ChannelBuffer buffer = ChannelBuffers.wrappedBuffer(content
.getContent());
request.setHeader(HttpHeaders.Names.CONTENT_LENGTH,
buffer.readableBytes());
request.setContent(buffer);
mergeHeaders(request, content);
}
public <T> void mergeHeaders(HttpRequest request, RiakObject<T> content) {
if (StringUtil.isEmpty(content.getContentType()) == false) {
request.setHeader(HttpHeaders.Names.CONTENT_TYPE,
content.getContentType());
}
// NOP content.getCharset();
if (StringUtil.isEmpty(content.getContentEncoding()) == false) {
request.setHeader(HttpHeaders.Names.CONTENT_ENCODING,
content.getContentEncoding());
}
// NOP content.getVtag();
if (content.getLinks() != null && content.getLinks().isEmpty() == false) {
addLinkHeader(request, content);
}
if (content.getLastModified() != null) {
request.setHeader(HttpHeaders.Names.LAST_MODIFIED,
HttpUtil.format(content.getLastModified()));
}
if (content.getUserMetadata() != null
&& content.getUserMetadata().isEmpty() == false) {
Map<String, String> map = content.getUserMetadata();
for (String key : map.keySet()) {
request.setHeader(RiakHttpHeaders.toUsermeta(key), map.get(key));
}
}
}
protected <T> void addLinkHeader(HttpRequest request, RiakObject<T> content) {
StringBuilder stb = new StringBuilder();
for (Link link : content.getLinks()) {
if (0 < stb.length()) {
stb.append(", ");
}
stb.append('<');
stb.append('/');
stb.append(this.config.getRawName());
stb.append('/');
stb.append(link.getLocation().getBucket());
stb.append('/');
stb.append(link.getLocation().getKey());
stb.append(">; riaktag=\"");
stb.append(link.getTag());
stb.append('"');
// https://github.com/basho/riak-java-client/pull/7
// MochiWeb has problem of too long header ?
if (2000 < stb.length()) {
request.addHeader(RiakHttpHeaders.LINK, stb.toString());
stb = new StringBuilder();
}
}
if (0 < stb.length()) {
request.addHeader(RiakHttpHeaders.LINK, stb.toString());
}
}
public RiakObject<byte[]> convert(HttpMessage headers,
ChannelBuffer buffer, Location location) {
DefaultRiakObject ro = new DefaultRiakObject(location);
byte[] bytes = new byte[buffer.readableBytes()];
buffer.readBytes(bytes);
ro.setContent(bytes);
convertHeaders(headers, ro);
return ro;
}
public <T> void convertHeaders(HttpMessage headers, RiakObject<T> ro) {
ro.setVectorClock(headers.getHeader(RiakHttpHeaders.VECTOR_CLOCK));
ro.setContentType(headers.getHeader(HttpHeaders.Names.CONTENT_TYPE));
// NOP ro.setCharset(charset);
ro.setContentEncoding(headers
.getHeader(HttpHeaders.Names.CONTENT_ENCODING));
// NOP ro.setVtag(vtag);
List<String> links = headers.getHeaders(RiakHttpHeaders.LINK);
ro.setLinks(parse(links));
String lastmod = headers.getHeader(HttpHeaders.Names.LAST_MODIFIED);
if (StringUtil.isEmpty(lastmod) == false) {
Date d = HttpUtil.parse(lastmod);
ro.setLastModified(d);
if (LOG.isDebugEnabled()) {
LOG.debug(Markers.DETAIL, Messages.LastModified, lastmod);
}
}
Map<String, String> map = new HashMap<String, String>();
for (String name : headers.getHeaderNames()) {
if (RiakHttpHeaders.isUsermeta(name)) {
String key = RiakHttpHeaders.fromUsermeta(name);
map.put(key, headers.getHeader(name));
}
}
ro.setUserMetadata(map);
}
static final Pattern LINK_PATTERN = Pattern
.compile("</\\w+/(\\w+)/(\\w+)>;\\s+riaktag=\"([^\"\\r\\n]+)\"");
static final int LINK_BUCKET = 1;
static final int LINK_KEY = 2;
static final int LINK_TAG = 3;
protected List<Link> parse(List<String> links) {
List<Link> result = new ArrayList<Link>();
for (String raw : links) {
Matcher m = LINK_PATTERN.matcher(raw);
while (m.find()) {
String b = m.group(LINK_BUCKET);
String k = m.group(LINK_KEY);
String t = m.group(LINK_TAG);
if (b != null && k != null && t != null) {
Link l = new Link(new Location(b, k), t);
result.add(l);
}
}
}
return result;
}
public HttpRequest newGetStreamRequest(String key) {
HttpRequest request = newRequest(this.host,
"/" + this.config.getLuwakName() + "/" + key, HttpMethod.GET);
return request;
}
public HttpRequest newStreamRequest(
final RiakObject<InputStreamHandler> content, String key,
HttpMethod method) {
HttpRequest request = newRequest(this.host,
"/" + this.config.getLuwakName() + "/" + key, method);
mergeHeaders(request, content);
request.setHeader(HttpHeaders.Names.EXPECT, HttpHeaders.Values.CONTINUE);
request.setHeader(HttpHeaders.Names.CONTENT_LENGTH, content
.getContent().getContentLength());
return request;
}
public HttpRequest newDeleteRequest(String key) {
HttpRequest request = newRequest(this.host,
"/" + this.config.getLuwakName() + "/" + key, HttpMethod.DELETE);
return request;
}
}