Javadoc'd almost everything

This commit is contained in:
J. Brisbin
2010-11-23 10:28:25 -06:00
parent 2d856a362c
commit 59ac3c5d4b
20 changed files with 570 additions and 340 deletions

View File

@@ -20,13 +20,26 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Specify the bucket in which to store the annotated object, overriding the
* default classname method of deriving bucket name.
*
* @author J. Brisbin <jon@jbrisbin.com>
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface KeyValueStoreMetaData {
/**
* The bucket in which to store an instance of this object.
*
* @return
*/
String bucket();
/**
* The media type in which to covert and store this object.
*
* @return
*/
String mediaType() default "application/json";
}

View File

@@ -1,12 +1,25 @@
package org.springframework.datastore.riak.core;
/**
* A generic interface for representing composite keys in data stores that use a
* bucket and key pair.
*
* @author J. Brisbin <jon@jbrisbin.com>
*/
public interface BucketKeyPair<B, K> {
/**
* Get the bucket representation.
*
* @return
*/
B getBucket();
/**
* Get the key representation.
*
* @return
*/
K getKey();
}

View File

@@ -1,11 +1,27 @@
package org.springframework.datastore.riak.core;
/**
* A generic interface to a resolver to turn a single object into a {@link
* org.springframework.datastore.riak.core.BucketKeyPair}.
*
* @author J. Brisbin <jon@jbrisbin.com>
*/
public interface BucketKeyResolver {
/**
* Can this resolver deal with the given object?
*
* @param o
* @param <V>
* @return
*/
<V> boolean canResolve(V o);
/**
* Turn the given object into a BucketKeyPair.
*
* @param o
* @return
*/
<B, K, V> BucketKeyPair<B, K> resolve(V o);
}

View File

@@ -5,12 +5,24 @@ import org.springframework.http.MediaType;
import java.util.Map;
/**
* A generic interface to MetaData provided by Key/Value data stores.
*
* @author J. Brisbin <jon@jbrisbin.com>
*/
public interface KeyValueStoreMetaData {
/**
* Get the Content-Type of this object.
*
* @return
*/
MediaType getContentType();
/**
* Get the arbitrary properties for this object.
*
* @return
*/
Map<String, Object> getProperties();
}

View File

@@ -18,57 +18,232 @@ package org.springframework.datastore.riak.core;
import java.util.List;
import java.util.Map;
/**
* Generic abstraction for Key/Value stores. Contains most operations that
* generic K/V stores might expose.
*/
public interface KeyValueStoreOperations {
// Set and Set with expiry operations
// Set operations
/**
* Set a value at a specified key.
*
* @param key
* @param value
* @return This template interface
*/
<K, V> KeyValueStoreOperations set(K key, V value);
/**
* Set a value as a byte array at a specified key.
*
* @param key
* @param value
* @return This template interface
*/
<K> KeyValueStoreOperations setAsBytes(K key, byte[] value);
// Get operations
/**
* Get a value at the specified key, trying to infer the type from either the
* bucket in which the value was stored, or (by default) as a
* <code>java.util.Map</code>.
*
* @param key
* @return The converted value, or <code>null</code> if not found.
*/
<K, V> V get(K key);
/**
* Get the value at the specified key as a byte array.
*
* @param key
* @return The byte array of data, or <code>null</code> if not found.
*/
<K> byte[] getAsBytes(K key);
/**
* Get the value at the specified key and convert it into an instance of the
* specified type.
*
* @param key
* @param requiredType
* @return The converted value, or <code>null</code> if not found.
*/
<K, T> T getAsType(K key, Class<T> requiredType);
// Get and Set operations
/**
* Get the old value at the specified key and replace it with the given
* value.
*
* @param key
* @param value
* @return The old value (before it was overwritten).
*/
<K, V> V getAndSet(K key, V value);
/**
* Get the old value at the specified key as a byte array and replace it with
* the given bytes.
*
* @param key
* @param value
* @return The old byte array (before it was overwritten).
*/
<K> byte[] getAndSetAsBytes(K key, byte[] value);
/**
* Get the old value at the specified key and replace it with the given value,
* converting it to an instance of the given type.
*
* @param key
* @param value
* @param requiredType The type to convert the value to.
* @return The old value (before it was overwritten).
*/
<K, V, T> T getAndSetAsType(K key, V value, Class<T> requiredType);
// Multi-get operations
/**
* Get all the values at the specified keys.
*
* @param keys
* @return A list of the values retrieved or an empty list if none were
* found.
*/
<K, V> List<V> getValues(List<K> keys);
/**
* Variation on {@link KeyValueStoreOperations#getValues(java.util.List)} that
* uses varargs instead of a <code>java.util.List</code>.
*
* @param keys
* @return A list of the values retrieved or an empty list if none were
* found.
*/
<K, V> List<V> getValues(K... keys);
/**
* Get all the values at the specified keys, converting the values into
* instances of the specified type.
*
* @param keys
* @param requiredType
* @return A list of the values retrieved or an empty list if none were
* found.
*/
<K, T> List<T> getValuesAsType(List<K> keys, Class<T> requiredType);
/**
* A variation on {@link KeyValueStoreOperations#getValuesAsType(java.util.List,
* Class)} that takes uses varargs instead of a <code>java.util.List</code>.
*
* @param requiredType
* @param keys
* @return A list of the values retrieved or an empty list if none were
* found.
*/
<T, K> List<T> getValuesAsType(Class<T> requiredType, K... keys);
// Set if non-existent operations
/**
* Set the value at the given key only if that key doesn't already exist.
*
* @param key
* @param value
* @return This template interface
*/
<K, V> KeyValueStoreOperations setIfKeyNonExistent(K key, V value);
/**
* Set the value at the given key as a byte array only if that key doesn't
* already exist.
*
* @param key
* @param value
* @return This template interface
*/
<K> KeyValueStoreOperations setIfKeyNonExistentAsBytes(K key, byte[] value);
// Multiple key-value set
/**
* Convenience method to set multiple values as Key/Value pairs.
*
* @param keysAndValues
* @return This template interface
*/
<K, V> KeyValueStoreOperations setMultiple(Map<K, V> keysAndValues);
/**
* Convenience method to set multiple values as Key/byte[] pairs.
*
* @param keysAndValues
* @return This template interface
*/
<K> KeyValueStoreOperations setMultipleAsBytes(Map<K, byte[]> keysAndValues);
// Multiple key-value set if non-existent
/**
* Variation on setting multiple values only if the key doesn't already
* exist.
*
* @param keysAndValues
* @return This template interface
*/
<K, V> KeyValueStoreOperations setMultipleIfKeysNonExistent(Map<K, V> keysAndValues);
/**
* Variation on setting multiple values as byte arryas only if the key doesn't
* already exist.
*
* @param keysAndValues
* @param <K>
* @return
*/
<K> KeyValueStoreOperations setMultipleAsBytesIfKeysNonExistent(Map<K, byte[]> keysAndValues);
/**
* Does the store contain this key?
*
* @param key
* @return <code>true</code> if the key exists, <code>false</code> otherwise.
*/
<K> boolean containsKey(K key);
/**
* Delete one or more keys from the store.
*
* @param keys
* @return <code>true</code> if all keys were successfully deleted,
* <code>false</code> otherwise.
*/
<K> boolean deleteKeys(K... keys);
/**
* Get the properties of the specified bucket.
*
* @param bucket
* @return The bucket properties, without a list of keys in that bucket.
*/
<B> Map<String, Object> getBucketSchema(B bucket);
/**
* Get the properties of the bucket and specify whether or not to list the
* keys in that bucket.
*
* @param bucket
* @param listKeys
* @return The bucket properties, with or without a list of keys in that
* bucket.
*/
<B> Map<String, Object> getBucketSchema(B bucket, boolean listKeys);
}

View File

@@ -1,12 +1,24 @@
package org.springframework.datastore.riak.core;
/**
* A generic interface for dealing with values and their store metadata.
*
* @author J. Brisbin <jon@jbrisbin.com>
*/
public interface KeyValueStoreValue<T> {
/**
* Get the metadata associated with this value.
*
* @return
*/
KeyValueStoreMetaData getMetaData();
/**
* Get the converted value itself.
*
* @return
*/
T get();
}

View File

@@ -5,6 +5,9 @@ import org.springframework.http.MediaType;
import java.util.Map;
/**
* An implementation of {@link org.springframework.datastore.riak.core.KeyValueStoreMetaData}
* for Riak.
*
* @author J. Brisbin <jon@jbrisbin.com>
*/
public class RiakMetaData implements KeyValueStoreMetaData {

View File

@@ -61,41 +61,112 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* An implementation of {@link org.springframework.datastore.riak.core.KeyValueStoreOperations}
* and {@link org.springframework.datastore.riak.mapreduce.MapReduceOperations}
* for the Riak data store.
* <p/>
* To use the RiakTemplate, create a singleton in your Spring
* application-context.xml:
* <pre><code>
* &lt;bean id="riak" class="org.springframework.datastore.riak.core.RiakTemplate"
* p:defaultUri="http://localhost:8098/riak/{bucket}/{key}"
* p:mapReduceUri="http://localhost:8098/mapred"/>
* </code></pre>
* To store and retrieve objects in Riak, use the setXXX and getXXX methods
* (example in Groovy):
* <pre><code>
* def obj = new TestObject(name: "My Name", age: 40)
* riak.set([bucket: "mybucket", key: "mykey"], obj)
* ...
* def name = riak.get([bucket: "mybucket", key: "mykey"]).name
* println "Hello $name!"
* </code></pre>
* You're key object should be one of: <ul><li>A <code>String</code> encoding
* the bucket and key together, separated by a colon. e.g. "mybucket:mykey"</li>
* <li>An implementation of BucketKeyPair (like {@link org.springframework.datastore.riak.core.SimpleBucketKeyPair})</li>
* <li>A <code>Map</code> with both a "bucket" and a "key" specified.</li> <li>A
* <code>String</code> of only the key name, but specifying a bucket by using
* the {@link org.springframework.datastore.riak.convert.KeyValueStoreMetaData}
* annotation on the object you're storing.</li></ul>
*
* @author J. Brisbin <jon@jbrisbin.com>
*/
@SuppressWarnings({"unchecked"})
public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOperations, MapReduceOperations, InitializingBean {
/**
* Client ID used by Riak to correlate updates.
*/
private static final String RIAK_CLIENT_ID = "org.springframework.datastore.riak.core.RiakTemplate/1.0";
private static final Pattern prefix = Pattern.compile("http[s]?://(\\S+):([0-9]+)/(\\S+)/\\{bucket\\}(\\S+)");
private static final boolean groovyPresent = ClassUtils.isPresent("org.codehaus.groovy.runtime.GStringImpl",
/**
* Regex used to extract host, port, and prefix from the given URI.
*/
private static final Pattern prefix = Pattern.compile(
"http[s]?://(\\S+):([0-9]+)/(\\S+)/\\{bucket\\}(\\S+)");
/**
* Do we need to handle Groovy strings in the Jackson JSON processor?
*/
private static final boolean groovyPresent = ClassUtils.isPresent(
"org.codehaus.groovy.runtime.GStringImpl",
RiakTemplate.class.getClassLoader());
private static SimpleDateFormat httpDate = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z");
/**
* For getting a <code>java.util.Date</code> from the Last-Modified header.
*/
private static SimpleDateFormat httpDate = new SimpleDateFormat(
"EEE, d MMM yyyy HH:mm:ss z");
protected final Logger log = LoggerFactory.getLogger(getClass());
/**
* For converting objects to/from other kinds of objects.
*/
protected ConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
/**
* For caching objects based on ETags.
*/
protected ConcurrentSkipListMap<BucketKeyPair, RiakValue<?>> cache = new ConcurrentSkipListMap<BucketKeyPair, RiakValue<?>>();
/**
* Whether or not to use the ETag-based cache.
*/
protected boolean useCache = true;
/**
* Not yet used.
*/
protected ExecutorService queue = Executors.newCachedThreadPool();
/**
* The URI to use inside the RestTemplate.
*/
protected String defaultUri = "http://localhost:8098/riak/{bucket}/{key}";
/**
* The URI for the Riak Map/Reduce API.
*/
protected String mapReduceUri = "http://localhost:8098/mapred";
/**
* A list of resolvers to turn a single object into a {#link BucketKeyPair}.
*/
protected List<BucketKeyResolver> bucketKeyResolvers;
/**
* Take all the defaults.
*/
public RiakTemplate() {
setRestTemplate(new RestTemplate());
}
/**
* Use the specified {@link org.springframework.http.client.ClientHttpRequestFactory}.
*
* @param requestFactory
*/
public RiakTemplate(ClientHttpRequestFactory requestFactory) {
super(requestFactory);
}
public RiakTemplate(String defaultUri) {
setRestTemplate(new RestTemplate());
setDefaultUri(defaultUri);
}
/**
* Use the specified defaultUri and mapReduceUri.
*
* @param defaultUri
* @param mapReduceUri
*/
public RiakTemplate(String defaultUri, String mapReduceUri) {
setRestTemplate(new RestTemplate());
this.setDefaultUri(defaultUri);
@@ -106,6 +177,11 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe
return conversionService;
}
/**
* Specify the conversion service to use.
*
* @param conversionService
*/
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
@@ -130,6 +206,11 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe
return bucketKeyResolvers;
}
/**
* Set the list of BucketKeyResolvers to use.
*
* @param bucketKeyResolvers
*/
public void setBucketKeyResolvers(List<BucketKeyResolver> bucketKeyResolvers) {
this.bucketKeyResolvers = bucketKeyResolvers;
}
@@ -142,6 +223,14 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe
this.useCache = useCache;
}
public String getPrefix() {
Matcher m = prefix.matcher(defaultUri);
if (m.matches()) {
return "/" + m.group(3);
}
return "/riak";
}
/*----------------- Set Operations -----------------*/
public <K, V> KeyValueStoreOperations set(K key, V value) {
@@ -151,7 +240,8 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe
public <K> KeyValueStoreOperations setAsBytes(K key, byte[] value) {
Assert.notNull(key, "Can't store an object with a NULL key.");
BucketKeyPair bucketKeyPair = resolveBucketKeyPair(key, value);
String bucketName = (null != bucketKeyPair.getBucket() ? bucketKeyPair.getBucket().toString() : "bytes");
String bucketName = (null != bucketKeyPair.getBucket() ? bucketKeyPair.getBucket()
.toString() : "bytes");
RestTemplate restTemplate = getRestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.set("X-Riak-ClientId", RIAK_CLIENT_ID);
@@ -159,7 +249,9 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe
HttpEntity<byte[]> entity = new HttpEntity<byte[]>(value, headers);
restTemplate.put(defaultUri, entity, bucketName, bucketKeyPair.getKey());
if (log.isDebugEnabled()) {
log.debug(String.format("PUT byte[]: bucket=%s, key=%s", bucketKeyPair.getBucket(), bucketKeyPair.getKey()));
log.debug(String.format("PUT byte[]: bucket=%s, key=%s",
bucketKeyPair.getBucket(),
bucketKeyPair.getKey()));
}
return this;
}
@@ -176,7 +268,10 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe
}
}
HttpEntity<V> entity = new HttpEntity<V>(value, headers);
restTemplate.put(defaultUri, entity, bucketKeyPair.getBucket(), bucketKeyPair.getKey());
restTemplate.put(defaultUri,
entity,
bucketKeyPair.getBucket(),
bucketKeyPair.getKey());
if (log.isDebugEnabled()) {
log.debug(String.format("PUT object: bucket=%s, key=%s, value=%s",
bucketKeyPair.getBucket(),
@@ -250,27 +345,32 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe
}
try {
RiakValue<byte[]> bytes = (RiakValue<byte[]>) restTemplate.execute(defaultUri,
RiakValue<byte[]> bytes = (RiakValue<byte[]>) restTemplate.execute(
defaultUri,
HttpMethod.GET,
new RequestCallback() {
public void doWithRequest(ClientHttpRequest request) throws IOException {
public void doWithRequest(ClientHttpRequest request) throws
IOException {
List<MediaType> mediaTypes = new ArrayList<MediaType>();
mediaTypes.add(MediaType.APPLICATION_JSON);
request.getHeaders().setAccept(mediaTypes);
}
},
new ResponseExtractor<Object>() {
public Object extractData(ClientHttpResponse response) throws IOException {
public Object extractData(ClientHttpResponse response) throws
IOException {
InputStream in = response.getBody();
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buff = new byte[in.available()];
for (int bytesRead = in.read(buff); bytesRead > 0; bytesRead = in.read(buff)) {
for (int bytesRead = in.read(buff); bytesRead > 0; bytesRead = in.read(
buff)) {
out.write(buff, 0, bytesRead);
}
HttpHeaders headers = response.getHeaders();
RiakMetaData meta = extractMetaData(headers);
RiakValue<byte[]> val = new RiakValue<byte[]>(out.toByteArray(), meta);
RiakValue<byte[]> val = new RiakValue<byte[]>(out.toByteArray(),
meta);
return val;
}
},
@@ -351,7 +451,9 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe
set(key, value);
} else {
if (log.isDebugEnabled()) {
log.debug(String.format("key: %s already exists. Not adding %s", key, value));
log.debug(String.format("key: %s already exists. Not adding %s",
key,
value));
}
}
return this;
@@ -362,7 +464,9 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe
setAsBytes(key, value);
} else {
if (log.isDebugEnabled()) {
log.debug(String.format("key: %s already exists. Not adding %s", key, value));
log.debug(String.format("key: %s already exists. Not adding %s",
key,
value));
}
}
return this;
@@ -405,7 +509,9 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe
RestTemplate restTemplate = getRestTemplate();
HttpHeaders headers = null;
try {
headers = restTemplate.headForHeaders(defaultUri, bucketKeyPair.getBucket(), bucketKeyPair.getKey());
headers = restTemplate.headForHeaders(defaultUri,
bucketKeyPair.getBucket(),
bucketKeyPair.getKey());
} catch (ResourceAccessException e) {
}
return (null != headers);
@@ -442,7 +548,9 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe
public <T> T execute(MapReduceJob job, Class<T> targetType) {
RestTemplate restTemplate = getRestTemplate();
ResponseEntity<T> resp = restTemplate.postForEntity(mapReduceUri, job.toJson(), targetType);
ResponseEntity<T> resp = restTemplate.postForEntity(mapReduceUri,
job.toJson(),
targetType);
if (resp.hasBody()) {
return resp.getBody();
}
@@ -455,6 +563,14 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe
/*----------------- Link Operations -----------------*/
/**
* Use Riak's native Link mechanism to link two entries together.
*
* @param destination Key to the child object
* @param source Key to the parent object
* @param tag The tag for this relationship
* @return This template interface
*/
public <K1, K2> RiakTemplate link(K1 destination, K2 source, String tag) {
BucketKeyPair bkpFrom = resolveBucketKeyPair(source, null);
BucketKeyPair bkpTo = resolveBucketKeyPair(destination, null);
@@ -462,7 +578,8 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe
RiakValue<byte[]> fromObj = getAsBytesWithMetaData(source);
if (null == fromObj) {
throw new DataStoreOperationException("Cannot link from a non-existent source: " + source);
throw new DataStoreOperationException(
"Cannot link from a non-existent source: " + source);
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(fromObj.getMetaData().getContentType());
@@ -473,7 +590,11 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe
} else if (linksObj instanceof String) {
links.add(linksObj.toString());
}
links.add(String.format("<%s/%s/%s>; riaktag=\"%s\"", extractPrefix(), bkpTo.getBucket(), bkpTo.getKey(), tag));
links.add(String.format("<%s/%s/%s>; riaktag=\"%s\"",
getPrefix(),
bkpTo.getBucket(),
bkpTo.getKey(),
tag));
StringWriter sw = new StringWriter();
boolean needsComma = false;
for (String link : links) {
@@ -493,21 +614,34 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe
return this;
}
/**
* Incomplete implementation of Link Walking.
*
* @param source
* @param tag
* @return
*/
public <T, K> T linkWalk(K source, String tag) {
BucketKeyPair bkpSource = resolveBucketKeyPair(source, null);
RestTemplate restTemplate = getRestTemplate();
final List<MediaType> types = new ArrayList<MediaType>();
types.add(MediaType.ALL);
restTemplate.execute(defaultUri + "/_,{tag},_", HttpMethod.GET, new RequestCallback() {
public void doWithRequest(ClientHttpRequest request) throws IOException {
request.getHeaders().setAccept(types);
}
}, new ResponseExtractor<Object>() {
public Object extractData(ClientHttpResponse response) throws IOException {
response.getHeaders();
return null; //To change body of implemented methods use File | Settings | File Templates.
}
}, bkpSource.getBucket(),
restTemplate.execute(defaultUri + "/_,{tag},_",
HttpMethod.GET,
new RequestCallback() {
public void doWithRequest(ClientHttpRequest request) throws
IOException {
request.getHeaders().setAccept(types);
}
},
new ResponseExtractor<Object>() {
public Object extractData(ClientHttpResponse response) throws
IOException {
response.getHeaders();
return null; //To change body of implemented methods use File | Settings | File Templates.
}
},
bkpSource.getBucket(),
bkpSource.getKey(),
tag);
return null;
@@ -528,12 +662,14 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe
if (resp.hasBody()) {
return resp.getBody();
} else {
throw new DataStoreOperationException("Error encountered retrieving bucket schema (Status: " + resp.getStatusCode() + ")");
throw new DataStoreOperationException(
"Error encountered retrieving bucket schema (Status: " + resp.getStatusCode() + ")");
}
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(conversionService, "Must specify a valid ConversionService.");
Assert.notNull(conversionService,
"Must specify a valid ConversionService.");
if (null == bucketKeyResolvers) {
bucketKeyResolvers = new ArrayList<BucketKeyResolver>();
bucketKeyResolvers.add(new SimpleBucketKeyResolver());
@@ -548,7 +684,8 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe
mapper.setSerializerFactory(fac);
for (HttpMessageConverter converter : converters) {
if (converter instanceof MappingJacksonHttpMessageConverter) {
((MappingJacksonHttpMessageConverter) converter).setObjectMapper(mapper);
((MappingJacksonHttpMessageConverter) converter).setObjectMapper(
mapper);
}
}
}
@@ -569,24 +706,28 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe
if (null != resolver) {
bucketKeyPair = resolver.resolve(key);
if (null != val) {
Annotation meta = (val instanceof Class ? (Class) val : val.getClass()).getAnnotation(KeyValueStoreMetaData.class);
Annotation meta = (val instanceof Class ? (Class) val : val.getClass()).getAnnotation(
KeyValueStoreMetaData.class);
if (null != meta) {
String bucket = ((KeyValueStoreMetaData) meta).bucket();
if (null != bucket) {
return new SimpleBucketKeyPair<String, Object>(bucket, bucketKeyPair.getKey());
return new SimpleBucketKeyPair<String, Object>(bucket,
bucketKeyPair.getKey());
}
}
}
return bucketKeyPair;
}
throw new DataStoreOperationException(String.format("No resolvers available to resolve bucket/key pair from %s",
throw new DataStoreOperationException(String.format(
"No resolvers available to resolve bucket/key pair from %s",
key));
}
protected MediaType extractMediaType(Object value) {
MediaType mediaType = (value instanceof byte[] ? MediaType.APPLICATION_OCTET_STREAM : MediaType.APPLICATION_JSON);
if (value.getClass().getAnnotations().length > 0) {
KeyValueStoreMetaData meta = value.getClass().getAnnotation(KeyValueStoreMetaData.class);
KeyValueStoreMetaData meta = value.getClass()
.getAnnotation(KeyValueStoreMetaData.class);
if (null != meta) {
mediaType = MediaType.parseMediaType(meta.mediaType());
}
@@ -594,13 +735,15 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe
return mediaType;
}
protected RiakMetaData extractMetaData(HttpHeaders headers) throws IOException {
protected RiakMetaData extractMetaData(HttpHeaders headers) throws
IOException {
Map<String, Object> props = new LinkedHashMap<String, Object>();
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
List<String> val = entry.getValue();
Object prop = (1 == val.size() ? val.get(0) : val);
try {
if (entry.getKey().equals("Last-Modified") || entry.getKey().equals("Date")) {
if (entry.getKey().equals("Last-Modified") || entry.getKey()
.equals("Date")) {
prop = httpDate.parse(val.get(0));
}
} catch (ParseException e) {
@@ -637,8 +780,14 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe
String bucketName = (null != bucketKeyPair.getBucket() ? bucketKeyPair.getBucket()
.toString() : requiredType.getName());
RestTemplate restTemplate = getRestTemplate();
HttpHeaders resp = restTemplate.headForHeaders(defaultUri, bucketName, bucketKeyPair.getKey());
if (!obj.getMetaData().getProperties().get("ETag").toString().equals(resp.getETag())) {
HttpHeaders resp = restTemplate.headForHeaders(defaultUri,
bucketName,
bucketKeyPair.getKey());
if (!obj.getMetaData()
.getProperties()
.get("ETag")
.toString()
.equals(resp.getETag())) {
obj = null;
} else {
if (log.isDebugEnabled()) {
@@ -649,12 +798,4 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe
return (null != obj ? (T) obj.get() : null);
}
public String extractPrefix() {
Matcher m = prefix.matcher(defaultUri);
if (m.matches()) {
return "/" + m.group(3);
}
return "/riak";
}
}

View File

@@ -12,7 +12,8 @@ import java.util.regex.Pattern;
@SuppressWarnings({"unchecked"})
public class SimpleBucketKeyResolver implements BucketKeyResolver {
private static final boolean groovyPresent = ClassUtils.isPresent("org.codehaus.groovy.runtime.GStringImpl",
private static final boolean groovyPresent = ClassUtils.isPresent(
"org.codehaus.groovy.runtime.GStringImpl",
RiakTemplate.class.getClassLoader());
protected Pattern bucketColonKey = Pattern.compile("(.+):(.+)");
@@ -43,12 +44,13 @@ public class SimpleBucketKeyResolver implements BucketKeyResolver {
Map m = (Map) o;
Object bucket = m.get("bucket");
Object key = m.get("key");
bucketKeyPair = new SimpleBucketKeyPair<String, String>((null != bucket ? bucket.toString() : null),
bucketKeyPair = new SimpleBucketKeyPair<String, String>((null != bucket ? bucket
.toString() : null),
(null != key ? key.toString() : null));
} else if (o instanceof BucketKeyPair) {
bucketKeyPair = (BucketKeyPair) o;
} else if (groovyPresent && o instanceof GStringImpl) {
bucketKeyPair = resolve(((GStringImpl) o).toString());
bucketKeyPair = resolve(o.toString());
}
return bucketKeyPair;

View File

@@ -4,6 +4,10 @@ import java.util.LinkedHashMap;
import java.util.Map;
/**
* An implementation of {@link org.springframework.datastore.riak.mapreduce.MapReduceOperation}
* to represent an Erlang M/R function, which must be already defined inside the
* Riak server.
*
* @author J. Brisbin <jon@jbrisbin.com>
*/
@SuppressWarnings({"unchecked"})
@@ -20,10 +24,20 @@ public class ErlangMapReduceOperation implements MapReduceOperation {
setFunction(function);
}
/**
* Set the Erlang module this function is defined in.
*
* @param module
*/
public void setModule(String module) {
moduleFunction.put("module", module);
}
/**
* Set the name of this Erlang function.
*
* @param function
*/
public void setFunction(String function) {
moduleFunction.put("function", function);
}

View File

@@ -3,6 +3,9 @@ package org.springframework.datastore.riak.mapreduce;
import org.springframework.datastore.riak.core.BucketKeyPair;
/**
* An implementation of {@link org.springframework.datastore.riak.mapreduce.MapReduceOperation}
* to describe a Javascript language M/R function.
*
* @author J. Brisbin <jon@jbrisbin.com>
*/
public class JavascriptMapReduceOperation implements MapReduceOperation {
@@ -22,6 +25,11 @@ public class JavascriptMapReduceOperation implements MapReduceOperation {
return source;
}
/**
* Set the anonymous source to use for the M/R function.
*
* @param source
*/
public void setSource(String source) {
this.source = source;
}
@@ -30,6 +38,12 @@ public class JavascriptMapReduceOperation implements MapReduceOperation {
return bucketKeyPair;
}
/**
* Set the {@link org.springframework.datastore.riak.core.BucketKeyPair} to
* point to for the Javascript to use in this M/R function.
*
* @param bucketKeyPair
*/
public void setBucketKeyPair(BucketKeyPair bucketKeyPair) {
this.bucketKeyPair = bucketKeyPair;
}

View File

@@ -20,19 +20,56 @@ import java.util.List;
import java.util.concurrent.Callable;
/**
* A generic interface to representing a Map/Reduce job to a data store that
* supports that operation.
*
* @author J. Brisbin <jon@jbrisbin.com>
*/
public interface MapReduceJob<T> extends Callable {
List<Object> getInputs();
/**
* Get the list of inputs for this job.
*
* @return
*/
<V> List<V> getInputs();
/**
* Set the list of inputs for this job.
*
* @param keys
* @param <V>
* @return
*/
<V> MapReduceJob addInputs(List<V> keys);
/**
* Add a phase to this operation.
*
* @param phase
* @return
*/
MapReduceJob addPhase(MapReducePhase phase);
/**
* Set the static argument for this job.
*
* @param arg
*/
void setArg(T arg);
/**
* Get the static argument for this job.
*
* @param <T>
* @return
*/
<T> T getArg();
/**
* Convert this job into the appropriate JSON to send to the server.
*
* @return
*/
String toJson();
}

View File

@@ -17,10 +17,17 @@
package org.springframework.datastore.riak.mapreduce;
/**
* A generic interface to a Map/Reduce operation.
*
* @author J. Brisbin <jon@jbrisbin.com>
*/
public interface MapReduceOperation {
/**
* Get the implementation-specific representation of a Map/Reduce operation.
*
* @return
*/
Object getRepresentation();
}

View File

@@ -20,14 +20,37 @@ import java.util.List;
import java.util.concurrent.Future;
/**
* Generic interface to Map/Reduce in data stores that support it.
*
* @author J. Brisbin <jon@jbrisbin.com>
*/
public interface MapReduceOperations {
/**
* Execute a {@link org.springframework.datastore.riak.mapreduce.MapReduceJob}
* synchronously.
*
* @param job
* @return
*/
Object execute(MapReduceJob job);
/**
* Execute a MapReduceJob synchronously, converting the result into the given
* type.
*
* @param job
* @param targetType
* @return The converted value.
*/
<T> T execute(MapReduceJob job, Class<T> targetType);
/**
* Submit the job to run asynchronously.
*
* @param job
* @return The Future representing the submitted job.
*/
<T> Future<List<T>> submit(MapReduceJob job);
}

View File

@@ -17,6 +17,8 @@
package org.springframework.datastore.riak.mapreduce;
/**
* A generic interface to the phases of Map/Reduce jobs.
*
* @author J. Brisbin <jon@jbrisbin.com>
*/
public interface MapReducePhase {
@@ -27,10 +29,25 @@ public interface MapReducePhase {
Phase getPhase();
/**
* The language this phase is described in.
*
* @return
*/
String getLanguage();
/**
* Whether or not to keep the result of this phase.
*
* @return
*/
boolean getKeepResults();
/**
* Get the operation this phase will execute.
*
* @return
*/
MapReduceOperation getOperation();
}

View File

@@ -31,6 +31,9 @@ import java.util.List;
import java.util.Map;
/**
* An implementation of {@link org.springframework.datastore.riak.mapreduce.MapReduceJob}
* for the Riak data store.
*
* @author J. Brisbin <jon@jbrisbin.com>
*/
@SuppressWarnings({"unchecked"})
@@ -54,7 +57,7 @@ public class RiakMapReduceJob implements MapReduceJob {
this.riakTemplate = riakTemplate;
}
public List<Object> getInputs() {
public List getInputs() {
return this.inputs;
}
@@ -117,14 +120,17 @@ public class RiakMapReduceJob implements MapReduceJob {
Object repr = phase.getOperation().getRepresentation();
if (repr instanceof String) {
// Using source
json.writeStringField("source", String.format("%s", phase.getOperation().getRepresentation()));
json.writeStringField("source",
String.format("%s", phase.getOperation().getRepresentation()));
} else if (repr instanceof BucketKeyPair) {
BucketKeyPair pair = (BucketKeyPair) repr;
json.writeStringField("bucket", String.format("%s", pair.getBucket()));
json.writeStringField("bucket",
String.format("%s", pair.getBucket()));
json.writeStringField("key", String.format("%s", pair.getKey()));
} else if (repr instanceof Map) {
for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) repr).entrySet()) {
json.writeStringField(entry.getKey().toString(), entry.getValue().toString());
json.writeStringField(entry.getKey().toString(),
entry.getValue().toString());
}
}
if (phase.getKeepResults()) {

View File

@@ -17,6 +17,9 @@
package org.springframework.datastore.riak.mapreduce;
/**
* An implementation of {@link org.springframework.datastore.riak.mapreduce.MapReducePhase}
* for the Riak data store.
*
* @author J. Brisbin <jon@jbrisbin.com>
*/
public class RiakMapReducePhase implements MapReducePhase {

View File

@@ -1,76 +0,0 @@
/*
* Copyright 2010 the original author or authors.
*
* Licensed 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 org.springframework.datastore.riak.core;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.LinkedHashMap;
import java.util.Map;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/org/springframework/datastore/RiakTemplateTests.xml"})
@SuppressWarnings({"unchecked"})
public class RiakTemplateIntegrationTests {
@Autowired
ApplicationContext appCtx;
@Autowired
RiakTemplate riak;
public void testSet() {
Map obj = new LinkedHashMap();
obj.put("test", "value");
obj.put("test2", 12);
riak.set("test:test", obj);
}
@Test
public void testSetAsType() {
TestObject obj = new TestObject();
riak.set("test", obj);
}
public void testSetInferringType() {
Map obj = new LinkedHashMap();
obj.put("test", "value");
obj.put("test2", 12);
riak.set("test", obj);
}
public void testGetInferringType() {
Map obj = riak.get("java.util.LinkedHashMap:test");
assert null != obj;
assert 12 == (Integer) obj.get("test2");
}
@Test
public void testGetAsType() {
TestObject obj = riak.getAsType("test", TestObject.class);
assert null != obj;
}
@Test
public void conversions() {
}
}

View File

@@ -1,161 +0,0 @@
/*
* Copyright (c) 2010 by J. Brisbin <jon@jbrisbin.com>
*
* Licensed 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 org.springframework.datastore.riak.core
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.ApplicationContext
import org.springframework.test.context.ContextConfiguration
import spock.lang.Specification
/**
* @author J. Brisbin <jon@jbrisbin.com>
*/
@ContextConfiguration(locations = "/org/springframework/datastore/RiakTemplateTests.xml")
class RiakTemplateSpec extends Specification {
@Autowired
ApplicationContext appCtx
@Autowired
RiakTemplate riak
int run = 1
def "Test Map object with 'bucket:key' key"() {
given:
def i = run++
String val = "value $i"
def objIn = [test: "value $i", integer: 12]
riak.set("test:test", objIn)
when:
def objOut = riak.get("test:test")
then:
objOut.test == val
}
def "Test Map object with Map key"() {
given:
def i = run++
String val = "value $i"
def objIn = [test: val, integer: 12]
riak.set([bucket: "test", key: "test"], objIn)
when:
def objOut = riak.get([bucket: "test", key: "test"])
then:
objOut.test == val
}
def "Test custom object with 'bucket:key' key"() {
given:
TestObject objIn = new TestObject()
riak.set("test:test", objIn)
when:
TestObject objOut = riak.get("test:test")
then:
objOut.test == "value"
}
def "Test custom object with 'ClassName:key' key"() {
given:
TestObject objIn = new TestObject()
riak.set("test", objIn)
when:
TestObject objOut = riak.getAsType("test", TestObject)
then:
objOut.test == "value"
}
def "Test containsKey"() {
when:
def containsKey = riak.containsKey("test:test")
then:
true == containsKey
}
def "Test multiple get"() {
when:
def objs = riak.getValues(["test:test", "${TestObject.name}:test"])
then:
2 == objs.size()
}
def "Test getAndSet with Map"() {
given:
def i = run++
String val = "value $i"
def newObj = [test: val, integer: 12]
when:
def oldObj = riak.getAndSet("test:test", newObj)
then:
"value" == oldObj.test
}
def "Test deleteKeys"() {
when:
def deleted = riak.deleteKeys("test:test", "${TestObject.name}:test")
then:
true == deleted
}
def "Test setMultipleIfKeysNonExistent with Map"() {
given:
def newObj = [
"test:test": [test: "value", integer: 12],
"${TestObject.name}:test": [test: "value", integer: 12]
]
when:
def secondObj = riak.setMultipleIfKeysNonExistent(newObj).get("${TestObject.name}:test")
secondObj.test = "newValue"
def thirdObj = riak.setMultipleIfKeysNonExistent(["${TestObject.name}:test": secondObj]).get("${TestObject.name}:test")
then:
"value" == thirdObj.test
cleanup:
riak.deleteKeys("test:test", "${TestObject.name}:test")
}
}

View File

@@ -1,41 +0,0 @@
/*
* Copyright (c) 2010 by J. Brisbin <jon@jbrisbin.com>
*
* Licensed 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 org.springframework.datastore.riak.core;
/**
* @author J. Brisbin <jon@jbrisbin.com>
*/
public class TestObject {
String test = "value";
Integer integer = 12;
public String getTest() {
return test;
}
public void setTest(String test) {
this.test = test;
}
public Integer getInteger() {
return integer;
}
public void setInteger(Integer integer) {
this.integer = integer;
}
}