From 543614083494eff16c76e1500d842bb8bcd7a3b5 Mon Sep 17 00:00:00 2001 From: "J. Brisbin" Date: Tue, 16 Nov 2010 15:27:23 -0600 Subject: [PATCH] Support for linking objects, ETag-based caching --- .../datastore/riak/core/BucketSchema.java | 10 + .../riak/core/KeyValueStoreMetaData.java | 16 + .../riak/core/KeyValueStoreOperations.java | 4 + .../riak/core/KeyValueStoreValue.java | 12 + .../datastore/riak/core/RiakMetaData.java | 32 ++ .../datastore/riak/core/RiakTemplate.java | 299 +++++++++++++++--- .../datastore/riak/core/RiakValue.java | 24 ++ .../riak/core/SimpleBucketKeyPair.java | 12 +- .../riak/core/RiakTemplateSpec.groovy | 34 ++ spring-datastore-riak/template.mf | 3 +- 10 files changed, 402 insertions(+), 44 deletions(-) create mode 100644 spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/BucketSchema.java create mode 100644 spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/KeyValueStoreMetaData.java create mode 100644 spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/KeyValueStoreValue.java create mode 100644 spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/RiakMetaData.java create mode 100644 spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/RiakValue.java diff --git a/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/BucketSchema.java b/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/BucketSchema.java new file mode 100644 index 000000000..0ac26fe79 --- /dev/null +++ b/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/BucketSchema.java @@ -0,0 +1,10 @@ +package org.springframework.datastore.riak.core; + +/** + * @author J. Brisbin + */ +public interface BucketSchema { + + String getName(); + +} diff --git a/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/KeyValueStoreMetaData.java b/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/KeyValueStoreMetaData.java new file mode 100644 index 000000000..6eeb17f9c --- /dev/null +++ b/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/KeyValueStoreMetaData.java @@ -0,0 +1,16 @@ +package org.springframework.datastore.riak.core; + +import org.springframework.http.MediaType; + +import java.util.Map; + +/** + * @author J. Brisbin + */ +public interface KeyValueStoreMetaData { + + MediaType getContentType(); + + Map getProperties(); + +} diff --git a/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/KeyValueStoreOperations.java b/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/KeyValueStoreOperations.java index 81cecbfe1..dd5e112b0 100644 --- a/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/KeyValueStoreOperations.java +++ b/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/KeyValueStoreOperations.java @@ -67,4 +67,8 @@ public interface KeyValueStoreOperations { boolean deleteKeys(K... keys); + Map getBucketSchema(B bucket); + + Map getBucketSchema(B bucket, boolean listKeys); + } diff --git a/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/KeyValueStoreValue.java b/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/KeyValueStoreValue.java new file mode 100644 index 000000000..9474a0cb9 --- /dev/null +++ b/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/KeyValueStoreValue.java @@ -0,0 +1,12 @@ +package org.springframework.datastore.riak.core; + +/** + * @author J. Brisbin + */ +public interface KeyValueStoreValue { + + KeyValueStoreMetaData getMetaData(); + + T get(); + +} diff --git a/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/RiakMetaData.java b/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/RiakMetaData.java new file mode 100644 index 000000000..d2773e4ff --- /dev/null +++ b/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/RiakMetaData.java @@ -0,0 +1,32 @@ +package org.springframework.datastore.riak.core; + +import org.springframework.http.MediaType; + +import java.util.Map; + +/** + * @author J. Brisbin + */ +public class RiakMetaData implements KeyValueStoreMetaData { + + private MediaType mediaType = MediaType.APPLICATION_JSON; + private Map properties; + + public RiakMetaData(Map properties) { + this.properties = properties; + } + + public RiakMetaData(MediaType mediaType, Map properties) { + this.mediaType = mediaType; + this.properties = properties; + } + + public MediaType getContentType() { + return mediaType; + } + + public Map getProperties() { + return this.properties; + } + +} diff --git a/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/RiakTemplate.java b/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/RiakTemplate.java index dfeaf6bc4..933c1633c 100644 --- a/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/RiakTemplate.java +++ b/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/RiakTemplate.java @@ -32,24 +32,32 @@ import org.springframework.datastore.riak.mapreduce.MapReduceJob; import org.springframework.datastore.riak.mapreduce.MapReduceOperations; import org.springframework.datastore.riak.mapreduce.RiakMapReduceJob; import org.springframework.http.*; +import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.ResourceAccessException; -import org.springframework.web.client.RestTemplate; +import org.springframework.web.client.*; import org.springframework.web.client.support.RestGatewaySupport; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.lang.annotation.Annotation; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * @author J. Brisbin @@ -57,12 +65,17 @@ import java.util.concurrent.Future; @SuppressWarnings({"unchecked"}) public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOperations, MapReduceOperations, InitializingBean { + 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", RiakTemplate.class.getClassLoader()); + + private static SimpleDateFormat httpDate = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z"); + protected final Logger log = LoggerFactory.getLogger(getClass()); protected ConversionService conversionService = ConversionServiceFactory.createDefaultConversionService(); - protected ConcurrentSkipListMap cache = new ConcurrentSkipListMap(); - protected ObjectMapper mapper = new ObjectMapper(); + protected ConcurrentSkipListMap> cache = new ConcurrentSkipListMap>(); + protected boolean useCache = true; protected ExecutorService queue = Executors.newCachedThreadPool(); protected String defaultUri = "http://localhost:8098/riak/{bucket}/{key}"; @@ -77,6 +90,17 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe super(requestFactory); } + public RiakTemplate(String defaultUri) { + setRestTemplate(new RestTemplate()); + setDefaultUri(defaultUri); + } + + public RiakTemplate(String defaultUri, String mapReduceUri) { + setRestTemplate(new RestTemplate()); + this.setDefaultUri(defaultUri); + this.mapReduceUri = mapReduceUri; + } + public ConversionService getConversionService() { return conversionService; } @@ -109,10 +133,21 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe this.bucketKeyResolvers = bucketKeyResolvers; } + public boolean isUseCache() { + return useCache; + } + + public void setUseCache(boolean useCache) { + this.useCache = useCache; + } + + /*----------------- Set Operations -----------------*/ + public KeyValueStoreOperations set(K key, V value) { BucketKeyPair bucketKeyPair = resolveBucketKeyPair(key, value); RestTemplate restTemplate = getRestTemplate(); HttpHeaders headers = new HttpHeaders(); + headers.set("X-Riak-ClientId", RIAK_CLIENT_ID); headers.setContentType(extractMediaType(value)); HttpEntity entity = new HttpEntity(value, headers); restTemplate.put(defaultUri, entity, bucketKeyPair.getBucket(), bucketKeyPair.getKey()); @@ -131,6 +166,7 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe String bucketName = (null != bucketKeyPair.getBucket() ? bucketKeyPair.getBucket().toString() : "bytes"); RestTemplate restTemplate = getRestTemplate(); HttpHeaders headers = new HttpHeaders(); + headers.set("X-Riak-ClientId", RIAK_CLIENT_ID); headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); HttpEntity entity = new HttpEntity(value, headers); restTemplate.put(defaultUri, entity, bucketName, bucketKeyPair.getKey()); @@ -140,36 +176,10 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe return this; } - public V get(K key) { + /*----------------- Get Operations -----------------*/ + + public RiakValue getWithMetaData(K key, Class requiredType) { BucketKeyPair bucketKeyPair = resolveBucketKeyPair(key, null); - RestTemplate restTemplate = getRestTemplate(); - Class targetClass; - try { - targetClass = Class.forName(bucketKeyPair.getBucket().toString()); - } catch (Throwable ignored) { - targetClass = Map.class; - } - String bucketName = (null != bucketKeyPair.getBucket() ? bucketKeyPair.getBucket() - .toString() : targetClass.getName()); - if (log.isDebugEnabled()) { - log.debug(String.format("GET object: bucket=%s, key=%s", bucketName, bucketKeyPair.getKey())); - } - try { - return (V) restTemplate.getForObject(defaultUri, targetClass, bucketName, bucketKeyPair.getKey()); - } catch (HttpClientErrorException e) { - if (e.getStatusCode() != HttpStatus.NOT_FOUND) { - throw new DataAccessResourceFailureException(e.getMessage(), e); - } - return null; - } - } - - public byte[] getAsBytes(K key) { - return getAsType(key, byte[].class); - } - - public T getAsType(K key, Class requiredType) { - BucketKeyPair bucketKeyPair = resolveBucketKeyPair(key, requiredType); String bucketName = (null != bucketKeyPair.getBucket() ? bucketKeyPair.getBucket() .toString() : requiredType.getName()); RestTemplate restTemplate = getRestTemplate(); @@ -179,14 +189,101 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe bucketKeyPair.getKey(), requiredType.getName())); } + try { - return (T) restTemplate.getForObject(defaultUri, requiredType, bucketName, bucketKeyPair.getKey()); + ResponseEntity result = restTemplate.getForEntity(defaultUri, + requiredType, + bucketName, + bucketKeyPair.getKey()); + if (result.hasBody()) { + RiakMetaData meta = extractMetaData(result.getHeaders()); + RiakValue val = new RiakValue(result.getBody(), meta); + if (useCache) { + cache.put(bucketKeyPair, val); + } + return val; + } } catch (HttpClientErrorException e) { if (e.getStatusCode() != HttpStatus.NOT_FOUND) { - throw new DataAccessResourceFailureException(e.getMessage(), e); + throw new DataStoreOperationException(e.getMessage(), e); } - return null; + } catch (IOException e) { + log.error(e.getMessage(), e); } + return null; + } + + public V get(K key) { + BucketKeyPair bucketKeyPair = resolveBucketKeyPair(key, null); + Class targetClass; + try { + targetClass = Class.forName(bucketKeyPair.getBucket().toString()); + } catch (Throwable ignored) { + targetClass = Map.class; + } + return (V) getWithMetaData(bucketKeyPair, targetClass).get(); + } + + public byte[] getAsBytes(K key) { + return getAsBytesWithMetaData(key).get(); + } + + public RiakValue getAsBytesWithMetaData(K key) { + BucketKeyPair bucketKeyPair = resolveBucketKeyPair(key, null); + final RestTemplate restTemplate = getRestTemplate(); + if (log.isDebugEnabled()) { + log.debug(String.format("GET object: bucket=%s, key=%s, type=byte[]", + bucketKeyPair.getBucket(), + bucketKeyPair.getKey())); + } + + try { + RiakValue bytes = (RiakValue) restTemplate.execute(defaultUri, + HttpMethod.GET, + new RequestCallback() { + public void doWithRequest(ClientHttpRequest request) throws IOException { + List mediaTypes = new ArrayList(); + mediaTypes.add(MediaType.APPLICATION_JSON); + request.getHeaders().setAccept(mediaTypes); + } + }, + new ResponseExtractor() { + 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)) { + out.write(buff, 0, bytesRead); + } + + HttpHeaders headers = response.getHeaders(); + RiakMetaData meta = extractMetaData(headers); + RiakValue val = new RiakValue(out.toByteArray(), meta); + return val; + } + }, + bucketKeyPair.getBucket(), + bucketKeyPair.getKey()); + if (useCache) { + cache.put(bucketKeyPair, bytes); + } + return bytes; + } catch (HttpClientErrorException e) { + if (e.getStatusCode() != HttpStatus.NOT_FOUND) { + throw new DataStoreOperationException(e.getMessage(), e); + } + } + return null; + } + + public T getAsType(K key, Class requiredType) { + if (useCache) { + Object obj = checkCache(key, requiredType); + if (null != obj) { + return (T) obj; + } + } + return getWithMetaData(key, requiredType).get(); } public V getAndSet(K key, V value) { @@ -196,7 +293,7 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe } public byte[] getAndSetAsBytes(K key, byte[] value) { - byte[] old = getAsType(key, byte[].class); + byte[] old = getAsBytes(key); setAsBytes(key, value); return old; } @@ -234,6 +331,8 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe return getValuesAsType(keyList, requiredType); } + /*----------------- Only-Set-Once Operations -----------------*/ + public KeyValueStoreOperations setIfKeyNonExistent(K key, V value) { if (!containsKey(key)) { set(key, value); @@ -256,6 +355,8 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe return this; } + /*----------------- Multiple Item Operations -----------------*/ + public KeyValueStoreOperations setMultiple(Map keysAndValues) { for (Map.Entry entry : keysAndValues.entrySet()) { set(entry.getKey(), entry.getValue()); @@ -284,6 +385,8 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe return this; } + /*----------------- Key Operations -----------------*/ + public boolean containsKey(K key) { BucketKeyPair bucketKeyPair = resolveBucketKeyPair(key, null); RestTemplate restTemplate = getRestTemplate(); @@ -337,6 +440,52 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe return queue.submit(job); } + /*----------------- Link Operations -----------------*/ + + public RiakTemplate link(K1 destination, K2 source, String tag) { + BucketKeyPair bkpFrom = resolveBucketKeyPair(source, null); + BucketKeyPair bkpTo = resolveBucketKeyPair(destination, null); + RestTemplate restTemplate = getRestTemplate(); + + RiakValue fromObj = getAsBytesWithMetaData(source); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(fromObj.getMetaData().getContentType()); + Object linksObj = fromObj.getMetaData().getProperties().get("Link"); + List links = new ArrayList(); + if (linksObj instanceof List) { + links.addAll((List) linksObj); + } else if (linksObj instanceof String) { + links.add(linksObj.toString()); + } + links.add(String.format("<%s/%s/%s>; riaktag=\"%s\"", extractPrefix(), bkpTo.getBucket(), bkpTo.getKey(), tag)); + for (String link : links) { + headers.set("Link", link); + } + HttpEntity entity = new HttpEntity(fromObj.get(), headers); + restTemplate.put(defaultUri, entity, bkpFrom.getBucket(), bkpFrom.getKey()); + + return this; + } + + /*----------------- Bucket Operations -----------------*/ + + public Map getBucketSchema(B bucket) { + return getBucketSchema(bucket, false); + } + + public Map getBucketSchema(B bucket, boolean listKeys) { + RestTemplate restTemplate = getRestTemplate(); + ResponseEntity resp = restTemplate.getForEntity(defaultUri, + Map.class, + bucket, + (listKeys ? "?keys=true" : "")); + if (resp.hasBody()) { + return resp.getBody(); + } else { + throw new DataStoreOperationException("Error encountered retrieving bucket schema (Status: " + resp.getStatusCode() + ")"); + } + } + public void afterPropertiesSet() throws Exception { Assert.notNull(conversionService, "Must specify a valid ConversionService."); if (null == bucketKeyResolvers) { @@ -346,19 +495,22 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe if (groovyPresent) { // Native conversion for Groovy GString objects + ObjectMapper mapper = new ObjectMapper(); + CustomSerializerFactory fac = new CustomSerializerFactory(); + fac.addSpecificMapping(GStringImpl.class, ToStringSerializer.instance); + mapper.setSerializerFactory(fac); List> converters = getRestTemplate().getMessageConverters(); for (HttpMessageConverter converter : converters) { if (converter instanceof MappingJacksonHttpMessageConverter) { - ObjectMapper mapper = new ObjectMapper(); - CustomSerializerFactory fac = new CustomSerializerFactory(); - fac.addSpecificMapping(GStringImpl.class, ToStringSerializer.instance); - mapper.setSerializerFactory(fac); ((MappingJacksonHttpMessageConverter) converter).setObjectMapper(mapper); } } } } + + /*----------------- Utilities -----------------*/ + protected BucketKeyPair resolveBucketKeyPair(Object key, Object val) { BucketKeyResolver resolver = null; for (BucketKeyResolver r : bucketKeyResolvers) { @@ -396,4 +548,67 @@ public class RiakTemplate extends RestGatewaySupport implements KeyValueStoreOpe return mediaType; } + protected RiakMetaData extractMetaData(HttpHeaders headers) throws IOException { + Map props = new LinkedHashMap(); + for (Map.Entry> entry : headers.entrySet()) { + List val = entry.getValue(); + Object prop = (1 == val.size() ? val.get(0) : val); + try { + if (entry.getKey().equals("Last-Modified") || entry.getKey().equals("Date")) { + prop = httpDate.parse(val.get(0)); + } + } catch (ParseException e) { + log.error(e.getMessage(), e); + } + + if (entry.getKey().equals("Link")) { + List links = new ArrayList(); + for (String link : entry.getValue()) { + String[] parts = link.split(","); + for (String part : parts) { + String s = part.replaceAll("<(.+)>; rel=\"(\\S+)\"[,]?", "").trim(); + if (!"".equals(s)) { + links.add(s); + } + } + } + props.put("Link", links); + } else { + props.put(entry.getKey().toString(), prop); + } + } + props.put("ETag", headers.getETag()); + RiakMetaData meta = new RiakMetaData(headers.getContentType(), props); + + return meta; + } + + + protected T checkCache(K key, Class requiredType) { + BucketKeyPair bucketKeyPair = resolveBucketKeyPair(key, requiredType); + RiakValue obj = cache.get(bucketKeyPair); + if (null != obj) { + 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())) { + obj = null; + } else { + if (log.isDebugEnabled()) { + log.debug("Returning CACHED object: " + obj); + } + } + } + return (null != obj ? (T) obj.get() : null); + } + + public String extractPrefix() { + Matcher m = prefix.matcher(defaultUri); + if (m.matches()) { + return "/" + m.group(3); + } + return "/riak"; + } + } diff --git a/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/RiakValue.java b/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/RiakValue.java new file mode 100644 index 000000000..bb35d9cfc --- /dev/null +++ b/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/RiakValue.java @@ -0,0 +1,24 @@ +package org.springframework.datastore.riak.core; + +/** + * @author J. Brisbin + */ +@SuppressWarnings({"unchecked"}) +public class RiakValue implements KeyValueStoreValue { + + private Object delegate; + private KeyValueStoreMetaData metaData; + + public RiakValue(T delegate, KeyValueStoreMetaData metaData) { + this.delegate = delegate; + this.metaData = metaData; + } + + public KeyValueStoreMetaData getMetaData() { + return this.metaData; + } + + public T get() { + return (T) delegate; + } +} diff --git a/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/SimpleBucketKeyPair.java b/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/SimpleBucketKeyPair.java index f0b29731a..04c20bbc8 100644 --- a/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/SimpleBucketKeyPair.java +++ b/spring-datastore-riak/src/main/java/org/springframework/datastore/riak/core/SimpleBucketKeyPair.java @@ -4,7 +4,7 @@ package org.springframework.datastore.riak.core; * @author J. Brisbin */ @SuppressWarnings({"unchecked"}) -public class SimpleBucketKeyPair implements BucketKeyPair { +public class SimpleBucketKeyPair implements BucketKeyPair, Comparable { private Object bucket; private Object key; @@ -21,4 +21,14 @@ public class SimpleBucketKeyPair implements BucketKeyPair { public K getKey() { return (K) key; } + + public int compareTo(Object o) { + if (o instanceof SimpleBucketKeyPair) { + SimpleBucketKeyPair pair = (SimpleBucketKeyPair) o; + if (pair.getBucket().equals(bucket) && pair.getKey().equals(key)) { + return 0; + } + } + return -1; + } } diff --git a/spring-datastore-riak/src/test/groovy/org/springframework/datastore/riak/core/RiakTemplateSpec.groovy b/spring-datastore-riak/src/test/groovy/org/springframework/datastore/riak/core/RiakTemplateSpec.groovy index f02b40b9b..025142072 100644 --- a/spring-datastore-riak/src/test/groovy/org/springframework/datastore/riak/core/RiakTemplateSpec.groovy +++ b/spring-datastore-riak/src/test/groovy/org/springframework/datastore/riak/core/RiakTemplateSpec.groovy @@ -64,6 +64,26 @@ class RiakTemplateSpec extends Specification { } + def "Test getting bucket schema"() { + + when: + def schema = riak.getBucketSchema("test", true) + + then: + "test" == schema.props.name + + } + + def "Test get with metadata"() { + + when: + def val = riak.getWithMetaData([bucket: "test", key: "test"], LinkedHashMap) + + then: + val.metaData.properties["Server"].contains("WebMachine") + + } + def "Test containsKey"() { when: @@ -74,6 +94,20 @@ class RiakTemplateSpec extends Specification { } + def "Test linking"() { + + given: + riak.link("${TestObject.name}:test", "test:test", "test") + + when: + def val = riak.getWithMetaData("test:test", Map) + def result = val.metaData.properties["Link"].collect { it.contains("riaktag=\"test\"") } + + then: + 1 == result.size() + + } + def "Test multiple get"() { when: diff --git a/spring-datastore-riak/template.mf b/spring-datastore-riak/template.mf index 9d57c5fac..f7e5d7f23 100644 --- a/spring-datastore-riak/template.mf +++ b/spring-datastore-riak/template.mf @@ -21,5 +21,6 @@ Import-Template: org.aopalliance.*;version="[1.0.0, 2.0.0)";resolution:=optional, org.slf4j.*;version="[1.5.10, 2.0.0)", org.w3c.dom.*;version="0", + org.codehaus.jackson.*;version="[1.5.6, 1.5.6)", org.codehaus.jackson.map.*;version="[1.5.6, 1.5.6)", - + org.codehaus.groovy.runtime.*;version="[1.7.5, 2.0.0)",