com.basho.riak.client.RiakObject Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of riak-client Show documentation
Show all versions of riak-client Show documentation
HttpClient-based client for Riak
/*
* This file is provided to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.basho.riak.client;
import static com.basho.riak.client.util.CharsetUtils.*;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.AbstractHttpEntity;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.InputStreamEntity;
import com.basho.riak.client.http.util.ClientUtils;
import com.basho.riak.client.request.RequestMeta;
import com.basho.riak.client.request.RiakWalkSpec;
import com.basho.riak.client.response.FetchResponse;
import com.basho.riak.client.response.HttpResponse;
import com.basho.riak.client.response.RiakIORuntimeException;
import com.basho.riak.client.response.RiakResponseRuntimeException;
import com.basho.riak.client.response.StoreResponse;
import com.basho.riak.client.response.WalkResponse;
import com.basho.riak.client.util.Constants;
/**
* A (legacy REST) Riak object.
*
* @deprecated with the addition of a protocol buffers client in 0.14 all the
* existing REST client code should be in client.http.* this class
* has therefore been moved. Please use
* com.basho.riak.client.http.RiakObject
* instead.
* WARNING: This class will be REMOVED in the next version.
* Please see also IRiakClient, IRiakObject for the new API
* @see com.basho.riak.client.http.RiakObject
* @see IRiakClient
* @see IRiakObject
*/
@Deprecated
public class RiakObject {
private static final byte[] EMPTY = new byte[] {};
private RiakClient riak;
private String bucket;
private String key;
private byte[] value;
private List links;
private Map userMetaData;
private String contentType;
private String vclock;
private String lastmod;
private String vtag;
private InputStream valueStream;
private Long valueStreamLength;
/**
* Create an empty object. The content type defaults to
* application/octet-stream.
*
* @param riak
* Riak instance this object is associated with, which is used by
* the convenience methods in this class (e.g.
* {@link RiakObject#store()}).
* @param bucket
* The object's bucket
* @param key
* The object's key
* @param value
* The object's value
* @param contentType
* The object's content type which defaults to
* application/octet-stream if null.
* @param links
* Links to other objects
* @param userMetaData
* Custom metadata key-value pairs for this object
* @param vclock
* An opaque vclock assigned by Riak
* @param lastmod
* The last time this object was modified according to Riak
* @param vtag
* This object's entity tag assigned by Riak
*/
public RiakObject(RiakClient riak, String bucket, String key, byte[] value, String contentType,
List links, Map userMetaData, String vclock, String lastmod, String vtag) {
this.riak = riak;
this.bucket = bucket;
this.key = key;
this.vclock = vclock;
this.lastmod = lastmod;
this.vtag = vtag;
safeSetValue(value);
this.contentType = contentType == null ? Constants.CTYPE_OCTET_STREAM : contentType;
safeSetLinks(links);
safeSetUsermetaData(userMetaData);
}
public RiakObject(RiakClient riak, String bucket, String key) {
this(riak, bucket, key, null, null, null, null, null, null, null);
}
public RiakObject(RiakClient riak, String bucket, String key, byte[] value) {
this(riak, bucket, key, value, null, null, null, null, null, null);
}
public RiakObject(RiakClient riak, String bucket, String key, byte[] value, String contentType) {
this(riak, bucket, key, value, contentType, null, null, null, null, null);
}
public RiakObject(RiakClient riak, String bucket, String key, byte[] value, String contentType, List links) {
this(riak, bucket, key, value, contentType, links, null, null, null, null);
}
public RiakObject(RiakClient riak, String bucket, String key, byte[] value, String contentType,
List links, Map userMetaData) {
this(riak, bucket, key, value, contentType, links, userMetaData, null, null, null);
}
public RiakObject(String bucket, String key) {
this(null, bucket, key, null, null, null, null, null, null, null);
}
public RiakObject(String bucket, String key, byte[] value) {
this(null, bucket, key, value, null, null, null, null, null, null);
}
public RiakObject(String bucket, String key, byte[] value, String contentType) {
this(null, bucket, key, value, contentType, null, null, null, null, null);
}
public RiakObject(String bucket, String key, byte[] value, String contentType, List links) {
this(null, bucket, key, value, contentType, links, null, null, null, null);
}
public RiakObject(String bucket, String key, byte[] value, String contentType, List links,
Map userMetaData) {
this(null, bucket, key, value, contentType, links, userMetaData, null, null, null);
}
public RiakObject(String bucket, String key, byte[] value, String contentType, List links,
Map userMetaData, String vclock, String lastmod, String vtag) {
this(null, bucket, key, value, contentType, links, userMetaData, vclock, lastmod, vtag);
}
/**
* A {@link RiakObject} can be loosely attached to the {@link RiakClient}
* from which retrieve it was retrieved. Calling convenience methods like
* {@link RiakObject#store()} will store this object use that client.
*/
public RiakClient getRiakClient() {
return riak;
}
public RiakObject setRiakClient(RiakClient client) {
riak = client;
return this;
}
/**
* Copy the metadata and value from object
. The bucket and key
* are not copied.
*
* @param object
* The source object to copy from
*/
public void copyData(RiakObject object) {
if (object == null)
return;
if (object.value != null) {
value = object.value.clone();
} else {
value = null;
}
valueStream = object.valueStream;
valueStreamLength = object.valueStreamLength;
setLinks(object.links);
userMetaData = new HashMap();
if (object.userMetaData != null) {
userMetaData.putAll(object.userMetaData);
}
contentType = object.contentType;
vclock = object.vclock;
lastmod = object.lastmod;
vtag = object.vtag;
}
/**
* Perform a shallow copy of the object
*/
void shallowCopy(RiakObject object) {
value = object.value;
this.links = object.links;
userMetaData = object.userMetaData;
contentType = object.contentType;
vclock = object.vclock;
lastmod = object.lastmod;
vtag = object.vtag;
valueStream = object.valueStream;
valueStreamLength = object.valueStreamLength;
}
/**
* Update the object's metadata. This usually happens when Riak returns
* updated metadata from a store operation.
*
* @param response
* Response from a store operation containing an updated vclock,
* last modified date, and vtag
*/
public void updateMeta(StoreResponse response) {
if (response == null) {
vclock = null;
lastmod = null;
vtag = null;
} else {
vclock = response.getVclock();
lastmod = response.getLastmod();
vtag = response.getVtag();
}
}
/**
* Update the object's metadata from a fetch or fetchMeta operation
*
* @param response
* Response from a fetch or fetchMeta operation containing a
* vclock, last modified date, and vtag
*/
public void updateMeta(FetchResponse response) {
if (response == null || response.getObject() == null) {
vclock = null;
lastmod = null;
vtag = null;
} else {
vclock = response.getObject().getVclock();
lastmod = response.getObject().getLastmod();
vtag = response.getObject().getVtag();
}
}
/**
* The object's bucket
*/
public String getBucket() {
return bucket;
}
/**
* The object's key
*/
public String getKey() {
return key;
}
/**
* The object's value
*/
public String getValue() {
return (value == null ? null : asString(value, getCharset(contentType)));
}
public byte[] getValueAsBytes() {
return value == null ? value : value.clone();
}
public void setValue(String value) {
if (value != null) {
this.value = asBytes(value, getCharset(contentType));
} else {
this.value = null;
}
}
public void setValue(byte[] value) {
safeSetValue(value);
}
/**
*
* @param value
*/
private void safeSetValue(final byte[] value) {
if(value != null) {
this.value = value.clone();
} else {
this.value = null;
}
}
/**
* Set the object's value as a stream. A value set here is independent of
* and has precedent over any value set using setValue():
* {@link RiakObject#writeToHttpMethod(HttpMethod)} will always write the
* value from getValueStream() if it is not null. Calling getValue() will
* always return values set via setValue(), and calling getValueStream()
* will always return the stream set via setValueStream.
*
* @param in
* Input stream representing the object's value
* @param len
* Length of the InputStream or null if unknown. If null, the
* value will be buffered in memory to determine its size before
* sending to the server.
*/
public void setValueStream(InputStream in, Long len) {
valueStream = in;
valueStreamLength = len;
}
public void setValueStream(InputStream in) {
valueStream = in;
}
public InputStream getValueStream() {
return valueStream;
}
public void setValueStreamLength(Long len) {
valueStreamLength = len;
}
public Long getValueStreamLength() {
return valueStreamLength;
}
/**
* The object's links -- may be empty, but never be null.
*
* @see {@link RiakObject#addLink()}, {@link RiakObject#removeLink()}, {@link RiakObject#iterator()}, {@link RiakObject#hasLinks()} and , {@link RiakObject#numLinks()}
*
* @return the list of {@link RiakLink}s for this
* RiakObject
* @deprecated please use {@link RiakObject#iterableLinks())} to iterate over the
* collection of {@link RiakLink}s. Attempting to mutate the
* collection will result in UnsupportedOperationException in
* future versions. Use {@link RiakObject#addLink()} and {@link RiakObject#removeLink()} instead.
* Use {@link RiakObject#hasLinks()}, {@link RiakObject#numLinks()} and {@link RiakObject#hasLink(RiakLink)}
* to query state of links.
*/
@Deprecated
public List getLinks() {
return this.links;
}
/**
* Makes a *deep* copy of links.
*
* Changes made to the original collection and its contents will not be reflected
* in this RiakObject's links. Use {@link RiakObject#addLink(RiakLink)},
* {@link RiakObject#removeLink(RiakLink)} and {@link RiakObject#setLinks(List)} to alter the collection.
* @param links a List of {@link RiakLink}
*/
public void setLinks(List links) {
safeSetLinks(links);
}
private void safeSetLinks(final List links) {
if (links == null) {
this.links = new CopyOnWriteArrayList();
} else {
this.links = new CopyOnWriteArrayList(deepCopy(links));
}
}
/**
* Creates a new RiakLink for each RiakLink in links and adds it to a new List.
*
* @param links a List of {@link RiakLink}s
* @return a deep copy of List.
*/
private List deepCopy(List links) {
final ArrayList copyLinks = new ArrayList();
for(RiakLink link : links) {
copyLinks.add(new RiakLink(link));
}
return copyLinks;
}
/**
* Add link to this RiakObject's links.
* @param link a {@link RiakLink} to add.
* @return this RiakObject.
*/
public RiakObject addLink(RiakLink link) {
if (link != null) {
links.add(link);
}
return this;
}
/**
* Remove a {@link RiakLink} from this RiakObject.
* @param link the {@link RiakLink} to remove
* @return this RiakObject
*/
public RiakObject removeLink(final RiakLink link) {
this.links.remove(link);
return this;
}
/**
* Does this RiakObject have any {@link RiakLink}s?
* @return true if there are links, false otherwise
*/
public boolean hasLinks() {
return !links.isEmpty();
}
/**
* How many {@link RiakLink}s does this RiakObject have?
* @return the number of {@link RiakLink}s this object has.
*/
public int numLinks() {
return links.size();
}
/**
* Checks if the collection of RiakLinks contains the one passed in.
* @param riakLink a RiakLink
* @return true if the RiakObject's link collection contains riakLink.
*/
public boolean hasLink(final RiakLink riakLink) {
return links.contains(riakLink);
}
/**
* User-specified metadata for the object in the form of key-value pairs --
* may be empty, but never be null. New key-value pairs can be added using
* addUsermeta()
*
* @deprecated Future versions will return an unmodifiable view of the user meta. Please use
* {@link RiakObject#addUsermeta(String, String)},
* {@link RiakObject#removeUsermetaItem(String)},
* {@link RiakObject#setUsermeta(Map)},
* {@link RiakObject#hasUsermetaItem(String)},
* {@link RiakObject#hasUsermeta()} and
* {@link RiakObject#getUsermetaItem(String)} to mutate and query the User meta collection
*/
@Deprecated
public Map getUsermeta() {
return userMetaData;
}
/**
* Creates a copy of userMetaData. Changes made to the original collection will not be
* reflected in the RiakObject's state.
* @param userMetaData
*/
public void setUsermeta(final Map userMetaData) {
safeSetUsermetaData(userMetaData);
}
private void safeSetUsermetaData(final Map userMetaData) {
if (userMetaData == null) {
this.userMetaData = new ConcurrentHashMap();
} else {
this.userMetaData = new ConcurrentHashMap(userMetaData);
}
}
/**
* Adds the key, value to the collection of user meta for this object.
* @param key
* @param value
* @return this RiakObject.
*/
public RiakObject addUsermetaItem(String key, String value) {
userMetaData.put(key, value);
return this;
}
/**
* @return true if there are any user meta data set on this RiakObject.
*/
public boolean hasUsermeta() {
return !userMetaData.isEmpty();
}
/**
* @return how many user meta data items this RiakObject has.
*/
public int numUsermetaItems() {
return userMetaData.size();
}
/**
* @param key
* @return
*/
public boolean hasUsermetaItem(String key) {
return userMetaData.containsKey(key);
}
/**
* Get an item of user meta data.
* @param key the user meta data item key
* @return The value for the given key or null.
*/
public String getUsermetaItem(String key) {
return userMetaData.get(key);
}
/**
* @param key the key of the item to remove
*/
public void removeUsermetaItem(String key) {
userMetaData.remove(key);
}
public Iterable usermetaKeys() {
return userMetaData.keySet();
}
/**
* The object's content type as a MIME type
*/
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
if (contentType != null) {
this.contentType = contentType;
} else {
this.contentType = Constants.CTYPE_OCTET_STREAM;
}
}
/**
* The object's opaque vclock assigned by Riak
*/
public String getVclock() {
return vclock;
}
/**
* The modification date of the object determined by Riak
*/
public String getLastmod() {
return lastmod;
}
/**
* Convenience method to get the last modified header parsed into a Date
* object. Returns null if header is null, malformed, or cannot be parsed.
*/
public Date getLastmodAsDate() {
return ClientUtils.parseDate(lastmod);
}
/**
* An entity tag for the object assigned by Riak
*/
public String getVtag() {
return vtag;
}
/**
* Convenience method for calling
* {@link RiakClient#store(RiakObject, RequestMeta)} followed by
* {@link RiakObject#updateMeta(StoreResponse)}
*
* @throws IllegalStateException
* if this object was not fetched from a Riak instance, so there
* is not associated server to store it with.
*/
public StoreResponse store(RequestMeta meta) {
return store(riak, meta);
}
public StoreResponse store() {
return store(riak, null);
}
/**
* Store this object to a different Riak instance.
*
* @param riak
* Riak instance to store this object to
* @param meta
* Same as {@link RiakClient#store(RiakObject, RequestMeta)}
* @throws IllegalStateException
* if this object was not fetched from a Riak instance, so there
* is not associated server to store it with.
*/
public StoreResponse store(RiakClient riak, RequestMeta meta) {
if (riak == null)
throw new IllegalStateException("Cannot store an object without a RiakClient");
StoreResponse r = riak.store(this, meta);
if (r.isSuccess()) {
this.updateMeta(r);
}
return r;
}
/**
* Convenience method for calling {@link RiakClient#fetch(String, String)}
* followed by {@link RiakObject#copyData(RiakObject)}
*
* @param meta
* Same as {@link RiakClient#fetch(String, String, RequestMeta)}
* @throws IllegalStateException
* if this object was not fetched from a Riak instance, so there
* is not associated server to refetch it from.
*/
public FetchResponse fetch(RequestMeta meta) {
if (riak == null)
throw new IllegalStateException("Cannot fetch an object without a RiakClient");
FetchResponse r = riak.fetch(bucket, key, meta);
if (r.getObject() != null) {
RiakObject other = r.getObject();
shallowCopy(other);
r.setObject(this);
}
return r;
}
public FetchResponse fetch() {
return fetch(null);
}
/**
* Convenience method for calling
* {@link RiakClient#fetchMeta(String, String, RequestMeta)} followed by
* {@link RiakObject#updateMeta(FetchResponse)}
*
* @throws IllegalStateException
* if this object was not fetched from a Riak instance, so there
* is not associated server to refetch meta from.
*/
public FetchResponse fetchMeta(RequestMeta meta) {
if (riak == null)
throw new IllegalStateException("Cannot fetch meta for an object without a RiakClient");
FetchResponse r = riak.fetchMeta(bucket, key, meta);
if (r.isSuccess()) {
this.updateMeta(r);
}
return r;
}
public FetchResponse fetchMeta() {
return fetchMeta(null);
}
/**
* Convenience method for calling
* {@link RiakClient#delete(String, String, RequestMeta)}.
*
* @throws IllegalStateException
* if this object was not fetched from a Riak instance, so there
* is not associated server to delete from.
*/
public HttpResponse delete(RequestMeta meta) {
if (riak == null)
throw new IllegalStateException("Cannot delete an object without a RiakClient");
return riak.delete(bucket, key, meta);
}
public HttpResponse delete() {
return delete(null);
}
/**
* Convenience methods for building a link walk specification starting from
* this object and calling
* {@link RiakClient#walk(String, String, RiakWalkSpec)}
*
* @param bucket
* The bucket to follow object links to
* @param tag
* The link tags to follow from this object
* @param keep
* Whether to keep the output from this link walking step. If not
* specified, then the output is only kept from the last step.
* @return A {@link LinkBuilder} object to continue building the walk query
* or to run it.
*/
public LinkBuilder walk(String bucket, String tag, boolean keep) {
return new LinkBuilder().walk(bucket, tag, keep);
}
public LinkBuilder walk(String bucket, String tag) {
return new LinkBuilder().walk(bucket, tag);
}
public LinkBuilder walk(String bucket, boolean keep) {
return new LinkBuilder().walk(bucket, keep);
}
public LinkBuilder walk(String bucket) {
return new LinkBuilder().walk(bucket);
}
public LinkBuilder walk() {
return new LinkBuilder().walk();
}
public LinkBuilder walk(boolean keep) {
return new LinkBuilder().walk(keep);
}
/**
* Serializes this object to an existing {@link HttpMethod} which can be
* sent as an HTTP request. Specifically, sends the object's link,
* user-defined metadata and vclock as HTTP headers and the value as the
* body. Used by {@link RiakClient} to create PUT requests.
*/
public void writeToHttpMethod(HttpRequestBase httpMethod) {
// Serialize headers
String basePath = getBasePathFromHttpMethod(httpMethod);
writeLinks(httpMethod, basePath);
for (String name : userMetaData.keySet()) {
httpMethod.addHeader(Constants.HDR_USERMETA_REQ_PREFIX + name, userMetaData.get(name));
}
if (vclock != null) {
httpMethod.addHeader(Constants.HDR_VCLOCK, vclock);
}
// Serialize body
if (httpMethod instanceof HttpEntityEnclosingRequestBase) {
HttpEntityEnclosingRequestBase entityEnclosingMethod = (HttpEntityEnclosingRequestBase) httpMethod;
AbstractHttpEntity entity = null;
// Any value set using setValueAsStream() has precedent over value
// set using setValue()
if (valueStream != null) {
if (valueStreamLength != null && valueStreamLength >= 0) {
entity = new InputStreamEntity(valueStream, valueStreamLength);
} else {
// since apache http client 4.1 no longer supports buffering stream entities, but we can't change API
// behaviour, here we have to buffer the whole content
entity = new ByteArrayEntity(ClientUtils.bufferStream(valueStream));
}
} else if (value != null) {
entity = new ByteArrayEntity(value);
} else {
entity = new ByteArrayEntity(EMPTY);
}
entity.setContentType(contentType);
entityEnclosingMethod.setEntity(entity);
}
}
private void writeLinks(HttpRequestBase httpMethod, String basePath) {
StringBuilder linkHeader = new StringBuilder();
for (RiakLink link : this.links) {
if (linkHeader.length() > 0) {
linkHeader.append(", ");
}
linkHeader.append("<");
linkHeader.append(basePath);
linkHeader.append("/");
linkHeader.append(link.getBucket());
linkHeader.append("/");
linkHeader.append(link.getKey());
linkHeader.append(">; ");
linkHeader.append(Constants.LINK_TAG);
linkHeader.append("=\"");
linkHeader.append(link.getTag());
linkHeader.append("\"");
// To avoid (MochiWeb) problems with too long headers, flush if
// it grows too big:
if (linkHeader.length() > 2000) {
httpMethod.addHeader(Constants.HDR_LINK, linkHeader.toString());
linkHeader = new StringBuilder();
}
}
if (linkHeader.length() > 0) {
httpMethod.addHeader(Constants.HDR_LINK, linkHeader.toString());
}
}
String getBasePathFromHttpMethod(HttpRequestBase httpMethod) {
if (httpMethod == null || httpMethod.getURI() == null)
return "";
String path = httpMethod.getURI().getPath();
int idx = path.length() - 1;
// ignore any trailing slash
if (path.endsWith("/")) {
idx--;
}
// trim off last two path components
idx = path.lastIndexOf('/', idx);
idx = path.lastIndexOf('/', idx - 1);
if (idx <= 0)
return "";
return path.substring(0, idx);
}
/**
* Created by links() as a convenient way to build up link walking queries
*/
public class LinkBuilder {
private RiakWalkSpec walkSpec = new RiakWalkSpec();
public LinkBuilder walk() {
walkSpec.addStep(RiakWalkSpec.WILDCARD, RiakWalkSpec.WILDCARD);
return this;
}
public LinkBuilder walk(boolean keep) {
walkSpec.addStep(RiakWalkSpec.WILDCARD, RiakWalkSpec.WILDCARD, keep);
return this;
}
public LinkBuilder walk(String bucket) {
walkSpec.addStep(bucket, RiakWalkSpec.WILDCARD);
return this;
}
public LinkBuilder walk(String bucket, boolean keep) {
walkSpec.addStep(bucket, RiakWalkSpec.WILDCARD, keep);
return this;
}
public LinkBuilder walk(String bucket, String tag) {
walkSpec.addStep(bucket, tag);
return this;
}
public LinkBuilder walk(String bucket, String tag, boolean keep) {
walkSpec.addStep(bucket, tag, keep);
return this;
}
public String getWalkSpec() {
return walkSpec.toString();
}
/**
* Execute the link walking query by calling
* {@link RiakClient#walk(String, String, String, RequestMeta)}.
*
* @param meta
* Extra metadata to attach to the request such as HTTP
* headers or query parameters.
* @return See
* {@link RiakClient#walk(String, String, String, RequestMeta)}.
*
* @throws RiakIORuntimeException
* If an error occurs during communication with the Riak
* server.
* @throws RiakResponseRuntimeException
* If the Riak server returns a malformed response.
*/
public WalkResponse run(RequestMeta meta) {
if (riak == null)
throw new IllegalStateException("Cannot perform object link walk without a RiakClient");
return riak.walk(bucket, key, getWalkSpec(), meta);
}
public WalkResponse run() {
return run(null);
}
}
/**
* A thread safe, snapshot Iterable view of the state of this RiakObject's {@link RiakLink}s at call time.
* Modifications are *NOT* supported.
* @return Iterable for this RiakObject's {@link RiakLink}s
*/
public Iterable iterableLinks() {
return new Iterable() {
public Iterator iterator() {
return links.iterator();
}
};
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy