implement template using SDK 2.x + tests

This commit is contained in:
Simon Baslé
2015-06-29 16:44:10 +02:00
parent 86785f8b06
commit 0afeb0bf87
16 changed files with 2450 additions and 29 deletions

View File

@@ -0,0 +1,555 @@
/*
* Copyright 2013 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.data.couchbase.core;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.document.RawJsonDocument;
import com.couchbase.client.java.error.DocumentDoesNotExistException;
import com.couchbase.client.java.view.Stale;
import com.couchbase.client.java.view.ViewQuery;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.couchbase.TestApplicationConfig;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.mapping.Field;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Michael Nitschinger
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestApplicationConfig.class)
@TestExecutionListeners(CouchbaseTemplateViewListener.class)
public class CouchbaseTemplateTests {
@Autowired
private Bucket client;
@Autowired
private CouchbaseTemplate template;
private static final ObjectMapper MAPPER = new ObjectMapper();
private void removeIfExist(String key) {
try {
client.remove(key);
}
catch (DocumentDoesNotExistException e) {
//ignore
}
}
@Test
public void saveSimpleEntityCorrectly() throws Exception {
String id = "beers:awesome-stout";
String name = "The Awesome Stout";
boolean active = false;
Beer beer = new Beer(id).setName(name).setActive(active);
template.save(beer);
RawJsonDocument resultDoc = client.get(id, RawJsonDocument.class);
assertNotNull(resultDoc);
String result = resultDoc.content();
assertNotNull(result);
Map<String, Object> resultConv = MAPPER.readValue(result, new TypeReference<Map<String, Object>>() {});
assertEquals("org.springframework.data.couchbase.core.Beer", resultConv.get("_class"));
assertEquals(false, resultConv.get("is_active"));
assertEquals("The Awesome Stout", resultConv.get("name"));
}
@Test
public void saveDocumentWithExpiry() throws Exception {
String id = "simple-doc-with-expiry";
DocumentWithExpiry doc = new DocumentWithExpiry(id);
template.save(doc);
assertNotNull(client.get(id));
Thread.sleep(3000);
assertNull(client.get(id));
}
@Test
public void insertDoesNotOverride() throws Exception {
String id = "double-insert-test";
removeIfExist(id);
SimplePerson doc = new SimplePerson(id, "Mr. A");
template.insert(doc);
RawJsonDocument resultDoc = client.get(id, RawJsonDocument.class);
assertNotNull(resultDoc);
String result = resultDoc.content();
Map<String, String> resultConv = MAPPER.readValue(result, new TypeReference<Map<String, String>>() {});
assertEquals("Mr. A", resultConv.get("name"));
doc = new SimplePerson(id, "Mr. B");
template.insert(doc);
resultDoc = client.get(id, RawJsonDocument.class);
assertNotNull(resultDoc);
result = resultDoc.content();
resultConv = MAPPER.readValue(result, new TypeReference<Map<String, String>>() {});
assertEquals("Mr. A", resultConv.get("name"));
}
@Test
public void updateDoesNotInsert() {
String id = "update-does-not-insert";
SimplePerson doc = new SimplePerson(id, "Nice Guy");
template.update(doc);
assertNull(client.get(id));
}
@Test
public void removeDocument() {
String id = "beers:to-delete-stout";
Beer beer = new Beer(id);
template.save(beer);
Object result = client.get(id);
assertNotNull(result);
template.remove(beer);
result = client.get(id);
assertNull(result);
}
@Test
public void storeListsAndMaps() {
String id = "persons:lots-of-names";
List<String> names = new ArrayList<String>();
names.add("Michael");
names.add("Thomas");
names.add(null);
List<Integer> votes = new LinkedList<Integer>();
Map<String, Boolean> info1 = new HashMap<String, Boolean>();
info1.put("foo", true);
info1.put("bar", false);
info1.put("nullValue", null);
Map<String, Integer> info2 = new HashMap<String, Integer>();
ComplexPerson complex = new ComplexPerson(id, names, votes, info1, info2);
template.save(complex);
assertNotNull(client.get(id));
ComplexPerson response = template.findById(id, ComplexPerson.class);
assertEquals(names, response.getFirstnames());
assertEquals(votes, response.getVotes());
assertEquals(id, response.getId());
assertEquals(info1, response.getInfo1());
assertEquals(info2, response.getInfo2());
}
@Test
public void validFindById() {
String id = "beers:findme-stout";
String name = "The Findme Stout";
boolean active = true;
Beer beer = new Beer(id).setName(name).setActive(active);
template.save(beer);
Beer found = template.findById(id, Beer.class);
assertNotNull(found);
assertEquals(id, found.getId());
assertEquals(name, found.getName());
assertEquals(active, found.getActive());
}
@Test
public void shouldLoadAndMapViewDocs() {
ViewQuery query = ViewQuery.from("test_beers", "by_name");
query.stale(Stale.FALSE);
final List<Beer> beers = template.findByView(query, Beer.class);
assertTrue(beers.size() > 0);
for (Beer beer : beers) {
assertNotNull(beer.getId());
assertNotNull(beer.getName());
assertNotNull(beer.getActive());
}
}
@Test
public void shouldDeserialiseLongs() {
final long time = new Date().getTime();
SimpleWithLong simpleWithLong = new SimpleWithLong("simpleWithLong:simple", time);
template.save(simpleWithLong);
simpleWithLong = template.findById("simpleWithLong:simple", SimpleWithLong.class);
assertNotNull(simpleWithLong);
assertEquals(time, simpleWithLong.getValue());
}
@Test
public void shouldDeserialiseEnums() {
SimpleWithEnum simpleWithEnum = new SimpleWithEnum("simpleWithEnum:enum", SimpleWithEnum.Type.BIG);
template.save(simpleWithEnum);
simpleWithEnum = template.findById("simpleWithEnum:enum", SimpleWithEnum.class);
assertNotNull(simpleWithEnum);
assertEquals(simpleWithEnum.getType(), SimpleWithEnum.Type.BIG);
}
@Test
public void shouldDeserialiseClass() {
SimpleWithClass simpleWithClass = new SimpleWithClass("simpleWithClass:class", Integer.class);
simpleWithClass.setValue("The dish ran away with the spoon.");
template.save(simpleWithClass);
simpleWithClass = template.findById("simpleWithClass:class", SimpleWithClass.class);
assertNotNull(simpleWithClass);
assertThat(simpleWithClass.getValue(), equalTo("The dish ran away with the spoon."));
}
@Test
public void shouldHandleCASVersionOnInsert() throws Exception {
removeIfExist("versionedClass:1");
VersionedClass versionedClass = new VersionedClass("versionedClass:1", "foobar");
assertEquals(0, versionedClass.getVersion());
template.insert(versionedClass);
RawJsonDocument rawStored = client.get("versionedClass:1", RawJsonDocument.class);
assertEquals(rawStored.cas(), versionedClass.getVersion());
}
@Test
public void versionShouldNotUpdateOnSecondInsert() throws Exception {
removeIfExist("versionedClass:2");
VersionedClass versionedClass = new VersionedClass("versionedClass:2", "foobar");
template.insert(versionedClass);
long version1 = versionedClass.getVersion();
template.insert(versionedClass);
long version2 = versionedClass.getVersion();
assertTrue(version1 > 0);
assertTrue(version2 > 0);
assertEquals(version1, version2);
}
@Test
public void shouldSaveDocumentOnMatchingVersion() throws Exception {
removeIfExist("versionedClass:3");
VersionedClass versionedClass = new VersionedClass("versionedClass:3", "foobar");
template.insert(versionedClass);
long version1 = versionedClass.getVersion();
versionedClass.setField("foobar2");
template.save(versionedClass);
long version2 = versionedClass.getVersion();
assertTrue(version1 > 0);
assertTrue(version2 > 0);
assertNotEquals(version1, version2);
assertEquals("foobar2", template.findById("versionedClass:3", VersionedClass.class).getField());
}
@Test(expected = OptimisticLockingFailureException.class)
public void shouldNotSaveDocumentOnNotMatchingVersion() throws Exception {
removeIfExist("versionedClass:4");
VersionedClass versionedClass = new VersionedClass("versionedClass:4", "foobar");
template.insert(versionedClass);
RawJsonDocument toCompare = RawJsonDocument.create("versionedClass:4", "different");
assertNotNull(client.upsert(toCompare));
versionedClass.setField("foobar2");
//save (aka upsert) won't error in case of CAS mismatch anymore
template.update(versionedClass);
}
@Test
public void shouldUpdateDocumentOnMatchingVersion() throws Exception {
removeIfExist("versionedClass:5");
VersionedClass versionedClass = new VersionedClass("versionedClass:5", "foobar");
template.insert(versionedClass);
long version1 = versionedClass.getVersion();
versionedClass.setField("foobar2");
template.update(versionedClass);
long version2 = versionedClass.getVersion();
assertTrue(version1 > 0);
assertTrue(version2 > 0);
assertNotEquals(version1, version2);
assertEquals("foobar2", template.findById("versionedClass:5", VersionedClass.class).getField());
}
@Test(expected = OptimisticLockingFailureException.class)
public void shouldNotUpdateDocumentOnNotMatchingVersion() throws Exception {
removeIfExist("versionedClass:6");
VersionedClass versionedClass = new VersionedClass("versionedClass:6", "foobar");
template.insert(versionedClass);
RawJsonDocument toCompare = RawJsonDocument.create("versionedClass:6", "different");
assertNotNull(client.upsert(toCompare));
versionedClass.setField("foobar2");
template.update(versionedClass);
}
@Test
public void shouldLoadVersionPropertyOnFind() throws Exception {
removeIfExist("versionedClass:7");
VersionedClass versionedClass = new VersionedClass("versionedClass:7", "foobar");
template.insert(versionedClass);
assertTrue(versionedClass.getVersion() > 0);
VersionedClass foundClass = template.findById("versionedClass:7", VersionedClass.class);
assertEquals(versionedClass.getVersion(), foundClass.getVersion());
}
/**
* A sample document with just an id and property.
*/
@Document
static class SimplePerson {
@Id
private final String id;
@Field
private final String name;
public SimplePerson(String id, String name) {
this.id = id;
this.name = name;
}
}
/**
* A sample document that expires in 2 seconds.
*/
@Document(expiry = 2)
static class DocumentWithExpiry {
@Id
private final String id;
public DocumentWithExpiry(String id) {
this.id = id;
}
}
@Document
static class ComplexPerson {
@Id
private final String id;
@Field
private final List<String> firstnames;
@Field
private final List<Integer> votes;
@Field
private final Map<String, Boolean> info1;
@Field
private final Map<String, Integer> info2;
public ComplexPerson(String id, List<String> firstnames,
List<Integer> votes, Map<String, Boolean> info1,
Map<String, Integer> info2) {
this.id = id;
this.firstnames = firstnames;
this.votes = votes;
this.info1 = info1;
this.info2 = info2;
}
List<String> getFirstnames() {
return firstnames;
}
List<Integer> getVotes() {
return votes;
}
Map<String, Boolean> getInfo1() {
return info1;
}
Map<String, Integer> getInfo2() {
return info2;
}
String getId() {
return id;
}
}
@Document
static class SimpleWithLong {
@Id
private String id;
private long value;
SimpleWithLong(final String id, final long value) {
this.id = id;
this.value = value;
}
String getId() {
return id;
}
long getValue() {
return value;
}
void setValue(final long value) {
this.value = value;
}
}
static class SimpleWithEnum {
@Id
private String id;
private enum Type {
BIG
}
private Type type;
SimpleWithEnum(final String id, final Type type) {
this.id = id;
this.type = type;
}
String getId() {
return id;
}
void setId(final String id) {
this.id = id;
}
Type getType() {
return type;
}
void setType(final Type type) {
this.type = type;
}
}
static class SimpleWithClass {
@Id
private String id;
private Class<Integer> integerClass;
private String value;
SimpleWithClass(final String id, final Class<Integer> integerClass) {
this.id = id;
this.integerClass = integerClass;
}
String getId() {
return id;
}
void setId(final String id) {
this.id = id;
}
Class<Integer> getIntegerClass() {
return integerClass;
}
void setIntegerClass(final Class<Integer> integerClass) {
this.integerClass = integerClass;
}
String getValue() {
return value;
}
void setValue(final String value) {
this.value = value;
}
}
static class VersionedClass {
@Id
private String id;
@Version
private long version;
private String field;
VersionedClass(String id, String field) {
this.id = id;
this.field = field;
}
public String getId() {
return id;
}
public long getVersion() {
return version;
}
public String getField() {
return field;
}
public void setField(String field) {
this.field = field;
}
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2013 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.data.couchbase.core;
import java.util.Collections;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.view.DefaultView;
import com.couchbase.client.java.view.DesignDocument;
import com.couchbase.client.java.view.View;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
/**
* @author Michael Nitschinger
*/
public class CouchbaseTemplateViewListener extends DependencyInjectionTestExecutionListener {
@Override
public void beforeTestClass(final TestContext testContext) throws Exception {
Bucket client = (Bucket) testContext.getApplicationContext().getBean("couchbaseBucket");
populateTestData(client);
createAndWaitForDesignDocs(client);
}
private void populateTestData(Bucket client) {
CouchbaseTemplate template = new CouchbaseTemplate(client);
for (int i = 0; i < 100; i++) {
Beer b = new Beer("testbeer-" + i).setName("MyBeer " + i).setActive(true);
template.save(b);
}
}
private void createAndWaitForDesignDocs(Bucket client) {
String mapFunction = "function (doc, meta) { if(doc._class == "
+ "\"org.springframework.data.couchbase.core.Beer\") { emit(doc.name, null); } }";
View view = DefaultView.create("by_name", mapFunction);
DesignDocument designDoc = DesignDocument.create("test_beers", Collections.singletonList(view));
client.bucketManager().upsertDesignDocument(designDoc);
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.data.couchbase.config;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -29,12 +30,15 @@ import com.couchbase.client.java.env.DefaultCouchbaseEnvironment;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationCondition;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.convert.CustomConversions;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService;
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy;
import org.springframework.data.mapping.model.FieldNamingStrategy;
@@ -130,17 +134,63 @@ public abstract class AbstractCouchbaseConfiguration {
*/
@Bean(name = BeanNames.COUCHBASE_TEMPLATE)
public CouchbaseTemplate couchbaseTemplate() throws Exception {
//TODO use mappingCouchbaseConverter and translationService when implemented
return new CouchbaseTemplate(couchbaseClient());
return new CouchbaseTemplate(couchbaseClient(), mappingCouchbaseConverter(), translationService());
}
//TODO create beans for mappingCouchbaseConverter, translationService, couchbaseMappingContext when implemented
//TODO for mappingCouchbaseConverter, allow registering of customConversions
/**
* Creates a {@link MappingCouchbaseConverter} using the configured {@link #couchbaseMappingContext}.
*
* @throws Exception on Bean construction failure.
*/
@Bean
public MappingCouchbaseConverter mappingCouchbaseConverter() throws Exception {
MappingCouchbaseConverter converter = new MappingCouchbaseConverter(couchbaseMappingContext());
converter.setCustomConversions(customConversions());
return converter;
}
/**
* Creates a {@link TranslationService}.
*
* @return TranslationService, defaulting to JacksonTranslationService.
*/
@Bean
public TranslationService translationService() {
final JacksonTranslationService jacksonTranslationService = new JacksonTranslationService();
jacksonTranslationService.afterPropertiesSet();
return jacksonTranslationService;
}
/**
* Creates a {@link CouchbaseMappingContext} equipped with entity classes scanned from the mapping base package.
*
* @throws Exception on Bean construction failure.
*/
@Bean
public CouchbaseMappingContext couchbaseMappingContext() throws Exception {
CouchbaseMappingContext mappingContext = new CouchbaseMappingContext();
mappingContext.setInitialEntitySet(getInitialEntitySet());
mappingContext.setSimpleTypeHolder(customConversions().getSimpleTypeHolder());
mappingContext.setFieldNamingStrategy(fieldNamingStrategy());
return mappingContext;
}
/**
* Register custom Converters in a {@link CustomConversions} object if required. These
* {@link CustomConversions} will be registered with the {@link #mappingCouchbaseConverter()} and
* {@link #couchbaseMappingContext()}. Returns an empty {@link CustomConversions} instance by default.
*
* @return must not be {@literal null}.
*/
@Bean
public CustomConversions customConversions() {
return new CustomConversions(Collections.emptyList());
}
/**
* Scans the mapping base package for classes annotated with {@link Document}.
*
* @throws ClassNotFoundException if initial entity sets could not be loaded.
* @throws ClassNotFoundException if intial entity sets could not be loaded.
*/
protected Set<Class<?>> getInitialEntitySet() throws ClassNotFoundException {
String basePackage = getMappingBasePackage();
@@ -187,7 +237,6 @@ public abstract class AbstractCouchbaseConfiguration {
* @return the naming strategy.
*/
protected FieldNamingStrategy fieldNamingStrategy() {
//TODO implement a CouchbaseMappingContext, use this method (update link in javadoc)
return abbreviateFieldNames() ? new CamelCaseAbbreviatingFieldNamingStrategy() : PropertyNameFieldNamingStrategy.INSTANCE;
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2012-2015 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.data.couchbase.core;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
/**
* Defines the callback which will be wrapped and executed on a bucket.
*
* @author Michael Nitschinger
*/
public interface BucketCallback<T> {
/**
* The enclosed body will be executed on the connected bucket.
*
* @return the result of the enclosed execution.
* @throws TimeoutException if the enclosed operation timed out.
* @throws ExecutionException if the result could not be retrieved because of a thrown exception before.
* @throws InterruptedException if the enclosed operation was interrupted.
*/
T doInBucket() throws TimeoutException, ExecutionException, InterruptedException;
}

View File

@@ -17,7 +17,18 @@
package org.springframework.data.couchbase.core;
import java.util.Collection;
import java.util.List;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.PersistTo;
import com.couchbase.client.java.ReplicateTo;
import com.couchbase.client.java.query.Query;
import com.couchbase.client.java.query.QueryParams;
import com.couchbase.client.java.query.QueryResult;
import com.couchbase.client.java.query.Statement;
import com.couchbase.client.java.view.ViewQuery;
import com.couchbase.client.java.view.ViewResult;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
@@ -29,6 +40,259 @@ import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
*/
public interface CouchbaseOperations {
/**
* Save the given object.
* <p/>
* <p>When the document already exists (specified by its unique id), then it will be overriden. Otherwise it will be
* created.</p>
*
* @param objectToSave the object to store in the bucket.
*/
void save(Object objectToSave);
/**
* Save the given object.
* <p/>
* <p>When the document already exists (specified by its unique id), then it will be overriden. Otherwise it will be
* created.</p>
*
* @param objectToSave the object to store in the bucket.
*/
void save(Object objectToSave, PersistTo persistTo, ReplicateTo replicateTo);
/**
* Save a list of objects.
* <p/>
* <p>When one of the documents already exists (specified by its unique id), then it will be overriden. Otherwise it
* will be created.</p>
*
* @param batchToSave the list of objects to store in the bucket.
*/
void save(Collection<?> batchToSave);
/**
* Save a list of objects.
* <p/>
* <p>When one of the documents already exists (specified by its unique id), then it will be overriden. Otherwise it
* will be created.</p>
*
* @param batchToSave the list of objects to store in the bucket.
* @param persistTo the persistence constraint setting.
* @param replicateTo the replication constraint setting.
*/
void save(Collection<?> batchToSave, PersistTo persistTo, ReplicateTo replicateTo);
/**
* Insert the given object.
* <p/>
* <p>When the document already exists (specified by its unique id), then it will not be overriden. Use the
* {@link CouchbaseOperations#save} method for this task.</p>
*
* @param objectToInsert the object to add to the bucket.
*/
void insert(Object objectToInsert);
/**
* Insert the given object.
* <p/>
* <p>When the document already exists (specified by its unique id), then it will not be overriden. Use the
* {@link CouchbaseOperations#save} method for this task.</p>
*
* @param objectToInsert the object to add to the bucket.
* @param persistTo the persistence constraint setting.
* @param replicateTo the replication constraint setting.
*/
void insert(Object objectToInsert, PersistTo persistTo, ReplicateTo replicateTo);
/**
* Insert a list of objects.
* <p/>
* <p>When one of the documents already exists (specified by its unique id), then it will not be overriden. Use the
* {@link CouchbaseOperations#save} method for this.</p>
*
* @param batchToInsert the list of objects to add to the bucket.
*/
void insert(Collection<?> batchToInsert);
/**
* Insert a list of objects.
* <p/>
* <p>When one of the documents already exists (specified by its unique id), then it will not be overriden. Use the
* {@link CouchbaseOperations#save} method for this.</p>
*
* @param batchToInsert the list of objects to add to the bucket.
* @param persistTo the persistence constraint setting.
* @param replicateTo the replication constraint setting.
*/
void insert(Collection<?> batchToInsert, PersistTo persistTo, ReplicateTo replicateTo);
/**
* Update the given object.
* <p/>
* <p>When the document does not exist (specified by its unique id) it will not be created. Use the
* {@link CouchbaseOperations#save} method for this.</p>
*
* @param objectToUpdate the object to add to the bucket.
*/
void update(Object objectToUpdate);
/**
* Update the given object.
* <p/>
* <p>When the document does not exist (specified by its unique id) it will not be created. Use the
* {@link CouchbaseOperations#save} method for this.</p>
*
* @param objectToUpdate the object to add to the bucket.
* @param persistTo the persistence constraint setting.
* @param replicateTo the replication constraint setting.
*/
void update(Object objectToUpdate, PersistTo persistTo, ReplicateTo replicateTo);
/**
* Insert a list of objects.
* <p/>
* <p>If one of the documents does not exist (specified by its unique id), then it will not be created. Use the
* {@link CouchbaseOperations#save} method for this.</p>
*
* @param batchToUpdate the list of objects to add to the bucket.
*/
void update(Collection<?> batchToUpdate);
/**
* Insert a list of objects.
* <p/>
* <p>If one of the documents does not exist (specified by its unique id), then it will not be created. Use the
* {@link CouchbaseOperations#save} method for this.</p>
*
* @param batchToUpdate the list of objects to add to the bucket.
* @param persistTo the persistence constraint setting.
* @param replicateTo the replication constraint setting.
*/
void update(Collection<?> batchToUpdate, PersistTo persistTo, ReplicateTo replicateTo);
/**
* Find an object by its given Id and map it to the corresponding entity.
*
* @param id the unique ID of the document.
* @param entityClass the entity to map to.
* @return returns the found object or null otherwise.
*/
<T> T findById(String id, Class<T> entityClass);
//TODO add javadoc link to setIncludeDocs when GA
/**
* Query a View for a list of documents of type T.
* <p/>
* <p>There is no need to setIncludeDocs(boolean) explicitly, because it will be set to true all the
* time. It is valid to pass in a empty constructed {@link ViewQuery} object.</p>
* <p/>
* <p>This method does not work with reduced views, because they by design do not contain references to original
* objects. Use the provided {@link #queryView} method for more flexibility and direct access.</p>
*
* @param query the Query object (also specifying view design document and view name).
* @param entityClass the entity to map to.
* @return the converted collection
*/
<T> List<T> findByView(ViewQuery query, Class<T> entityClass);
/**
* Query a View with direct access to the {@link ViewResult}.
* <p>This method is available to ease the working with views by still wrapping exceptions into the Spring
* infrastructure.</p>
* <p>It is especially needed if you want to run reduced viewName queries, because they can't be mapped onto entities
* directly.</p>
*
* @param query the Query object (also specifying view design document and view name).
* @return ViewResult containing the results of the query.
*/
ViewResult queryView(ViewQuery query);
/**
* Query the N1QL Service for JSON data of type T. This is done via a {@link Query} that can
* contain a {@link Statement}, additional query parameters ({@link QueryParams})
* and placeholder values if the statement contains placeholders.
* <p>Use {@link Query}'s factory methods to construct this.</p>
*
* @param n1ql the N1QL query.
* @param entityClass the target class for the returned entities.
* @param <T> the entity class
* @return the list of entities matching this query.
*/
<T> List<T> findByN1QL(Query n1ql, Class<T> entityClass);
/**
* Query the N1QL Service with direct access to the {@link QueryResult}.
* <p>
* This is done via a {@link Query} that can
* contain a {@link Statement}, additional query parameters ({@link QueryParams})
* and placeholder values if the statement contains placeholders.</p>
* <p>
* Use {@link Query}'s factory methods to construct this.</p>
*
* @param n1ql the N1QL query.
* @return {@link QueryResult} containing the results of the n1ql query.
*/
QueryResult queryN1QL(Query n1ql);
/**
* Checks if the given document exists.
*
* @param id the unique ID of the document.
* @return whether the document could be found or not.
*/
boolean exists(String id);
/**
* Remove the given object from the bucket by id.
* <p/>
* If the object is a String, it will be treated as the document key
* directly.
*
* @param objectToRemove the Object to remove.
*/
void remove(Object objectToRemove);
/**
* Remove the given object from the bucket by id.
* <p/>
* If the object is a String, it will be treated as the document key
* directly.
*
* @param objectToRemove the Object to remove.
* @param persistTo the persistence constraint setting.
* @param replicateTo the replication constraint setting.
*/
void remove(Object objectToRemove, PersistTo persistTo, ReplicateTo replicateTo);
/**
* Remove a list of objects from the bucket by id.
*
* @param batchToRemove the list of Objects to remove.
*/
void remove(Collection<?> batchToRemove);
/**
* Remove a list of objects from the bucket by id.
*
* @param batchToRemove the list of Objects to remove.
* @param persistTo the persistence constraint setting.
* @param replicateTo the replication constraint setting.
*/
void remove(Collection<?> batchToRemove, PersistTo persistTo, ReplicateTo replicateTo);
/**
* Executes a BucketCallback translating any exceptions as necessary.
* <p/>
* Allows for returning a result object, that is a domain object or a collection of domain objects.
*
* @param action the action to execute in the callback.
* @param <T> the return type.
* @return the return type.
*/
<T> T execute(BucketCallback<T> action);
/**
* Returns the linked {@link Bucket} to this template.
*

View File

@@ -16,25 +16,47 @@
package org.springframework.data.couchbase.core;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.PersistTo;
import com.couchbase.client.java.ReplicateTo;
import com.couchbase.client.java.document.Document;
import com.couchbase.client.java.document.RawJsonDocument;
import com.couchbase.client.java.error.CASMismatchException;
import com.couchbase.client.java.query.Query;
import com.couchbase.client.java.query.QueryResult;
import com.couchbase.client.java.view.ViewQuery;
import com.couchbase.client.java.view.ViewResult;
import com.couchbase.client.java.view.ViewRow;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.dao.QueryTimeoutException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService;
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.couchbase.core.mapping.CouchbaseStorable;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.BeanWrapper;
/**
* @author Michael Nitschinger
@@ -46,6 +68,7 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
private static final Logger LOGGER = LoggerFactory.getLogger(CouchbaseTemplate.class);
private static final WriteResultChecking DEFAULT_WRITE_RESULT_CHECKING = WriteResultChecking.NONE;
private static final Collection<String> ITERABLE_CLASSES;
static {
final Set<String> iterableClasses = new HashSet<String>();
iterableClasses.add(List.class.getName());
@@ -61,6 +84,9 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
private ApplicationEventPublisher eventPublisher;
private WriteResultChecking writeResultChecking = DEFAULT_WRITE_RESULT_CHECKING;
private PersistenceExceptionTranslator exceptionTranslator = new CouchbaseExceptionTranslator();
protected final MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> mappingContext;
public CouchbaseTemplate(final Bucket client) {
this(client, null, null);
@@ -75,6 +101,7 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
this.client = client;
this.converter = converter == null ? getDefaultConverter() : converter;
this.translationService = translationService == null ? getDefaultTranslationService() : translationService;
this.mappingContext = this.converter.getMappingContext();
}
private TranslationService getDefaultTranslationService() {
@@ -89,15 +116,27 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
return c;
}
/** Encode a {@link CouchbaseStorable} into a storable representation (JSON) **/
private Object translateEncode(final CouchbaseStorable source) {
return translationService.encode(source);
/**
* Encode a {@link CouchbaseDocument} into a storable representation (JSON) then prepare
* it for storage as a {@link Document}.
*/
private Document<String> encodeAndWrap(final CouchbaseDocument source, Long version) {
String encodedContent = translationService.encode(source);
if (version == null) {
return RawJsonDocument.create(source.getId(), source.getExpiration(), encodedContent);
}
else {
return RawJsonDocument.create(source.getId(), source.getExpiration(), encodedContent, version);
}
}
/** Decode a JSON string into a {@link CouchbaseStorable} **/
private CouchbaseStorable translateDecode(final String source, final CouchbaseStorable target) {
return translationService.decode(source, target);
/**
* Decode a {@link Document Document&lt;String&gt;} containing a JSON string
* into a {@link CouchbaseStorable}
*/
private CouchbaseStorable decodeAndUnwrap(final Document<String> source, final CouchbaseStorable target) {
return translationService.decode(source.content(), target); //TODO rework and check
}
/**
@@ -118,15 +157,16 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
*
* @param message the message to use.
*/
private void handleWriteResultError(String message) {
private void handleWriteResultError(String message, Exception cause) {
if (writeResultChecking == WriteResultChecking.NONE) {
return;
}
if (writeResultChecking == WriteResultChecking.EXCEPTION) {
throw new CouchbaseDataIntegrityViolationException(message);
} else {
LOGGER.error(message);
throw new CouchbaseDataIntegrityViolationException(message, cause);
}
else {
LOGGER.error(message, cause);
}
}
@@ -139,6 +179,279 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
this.eventPublisher = eventPublisher;
}
@Override
public void save(Object objectToSave) {
save(objectToSave, PersistTo.NONE, ReplicateTo.NONE);
}
@Override
public void save(Object objectToSave, PersistTo persistTo, ReplicateTo replicateTo) {
doPersist(objectToSave, persistTo, replicateTo, false, false);
}
@Override
public void save(Collection<?> batchToSave) {
save(batchToSave, PersistTo.NONE, ReplicateTo.NONE);
}
@Override
public void save(Collection<?> batchToSave, PersistTo persistTo, ReplicateTo replicateTo) {
for (Object o : batchToSave) {
doPersist(o, persistTo, replicateTo, false, false);
}
}
@Override
public void insert(Object objectToInsert) {
insert(objectToInsert, PersistTo.NONE, ReplicateTo.NONE);
}
@Override
public void insert(Object objectToInsert, PersistTo persistTo, ReplicateTo replicateTo) {
doPersist(objectToInsert, persistTo, replicateTo, true, false);
}
@Override
public void insert(Collection<?> batchToInsert) {
insert(batchToInsert, PersistTo.NONE, ReplicateTo.NONE);
}
@Override
public void insert(Collection<?> batchToInsert, PersistTo persistTo, ReplicateTo replicateTo) {
for (Object o : batchToInsert) {
doPersist(o, persistTo, replicateTo, true, false);
}
}
@Override
public void update(Object objectToUpdate) {
update(objectToUpdate, PersistTo.NONE, ReplicateTo.NONE);
}
@Override
public void update(Object objectToUpdate, PersistTo persistTo, ReplicateTo replicateTo) {
doPersist(objectToUpdate, persistTo, replicateTo, false, true);
}
@Override
public void update(Collection<?> batchToUpdate) {
update(batchToUpdate, PersistTo.NONE, ReplicateTo.NONE);
}
@Override
public void update(Collection<?> batchToUpdate, PersistTo persistTo, ReplicateTo replicateTo) {
for (Object o : batchToUpdate) {
doPersist(o, persistTo, replicateTo, false, true);
}
}
@Override
public <T> T findById(final String id, Class<T> entityClass) {
RawJsonDocument result = execute(new BucketCallback<RawJsonDocument>() {
@Override
public RawJsonDocument doInBucket() {
return client.get(id, RawJsonDocument.class);
}
});
return mapToEntity(id, result, entityClass);
}
@Override
public <T> List<T> findByView(ViewQuery query, Class<T> entityClass) {
query.includeDocs(false);
query.reduce(false);
final ViewResult response = queryView(query);
List<ViewRow> allRows = response.allRows();
//TODO error handling
final List<T> result = new ArrayList<T>(allRows.size());
for (final ViewRow row : allRows) {
result.add(mapToEntity(row.id(), row.document(RawJsonDocument.class), entityClass));
}
return result;
}
@Override
public ViewResult queryView(final ViewQuery query) {
return execute(new BucketCallback<ViewResult>() {
@Override
public ViewResult doInBucket() {
return client.query(query);
}
});
}
@Override
public <T> List<T> findByN1QL(Query n1ql, Class<T> entityClass) {
//TODO find a way of mapping content to T
//TODO error handling
throw new NotImplementedException();
}
@Override
public QueryResult queryN1QL(final Query query) {
return execute(new BucketCallback<QueryResult>() {
@Override
public QueryResult doInBucket() throws TimeoutException, ExecutionException, InterruptedException {
return client.query(query);
}
});
}
@Override
public boolean exists(final String id) {
return execute(new BucketCallback<Boolean>() {
@Override
public Boolean doInBucket() throws TimeoutException, ExecutionException, InterruptedException {
return client.exists(id);
}
});
}
@Override
public void remove(Object objectToRemove) {
remove(objectToRemove, PersistTo.NONE, ReplicateTo.NONE);
}
@Override
public void remove(Object objectToRemove, PersistTo persistTo, ReplicateTo replicateTo) {
doRemove(objectToRemove, persistTo, replicateTo);
}
@Override
public void remove(Collection<?> batchToRemove) {
remove(batchToRemove, PersistTo.NONE, ReplicateTo.NONE);
}
@Override
public void remove(Collection<?> batchToRemove, PersistTo persistTo, ReplicateTo replicateTo) {
for (Object o : batchToRemove) {
doRemove(o, persistTo, replicateTo);
}
}
@Override
public <T> T execute(BucketCallback<T> action) {
try {
return action.doInBucket();
}
catch (RuntimeException e) {
throw exceptionTranslator.translateExceptionIfPossible(e);
}
catch (TimeoutException e) {
throw new QueryTimeoutException(e.getMessage(), e);
}
catch (InterruptedException e) {
throw new OperationInterruptedException(e.getMessage(), e);
}
catch (ExecutionException e) {
throw new OperationInterruptedException(e.getMessage(), e);
}
}
private void doPersist(Object objectToPersist, final PersistTo persistTo, final ReplicateTo replicateTo,
final boolean failOnExist, final boolean failOnMissing) {
ensureNotIterable(objectToPersist);
final String operationDesc = failOnExist ? "Insert" : failOnMissing ? "Update" : "Upsert";
final BeanWrapper<Object> beanWrapper = BeanWrapper.create(objectToPersist, converter.getConversionService());
CouchbasePersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(objectToPersist.getClass());
final CouchbasePersistentProperty versionProperty = persistentEntity.getVersionProperty();
final Long version = versionProperty != null ? beanWrapper.getProperty(versionProperty, Long.class) : null;
//TODO event beforeConvert
final CouchbaseDocument converted = new CouchbaseDocument();
converter.write(objectToPersist, converted);
//TODO event beforeSave
execute(new BucketCallback<Boolean>() {
@Override
public Boolean doInBucket() throws InterruptedException, ExecutionException {
Document<String> doc = encodeAndWrap(converted, version);
Document<String> storedDoc;
try {
if (!failOnExist && !failOnMissing) {
storedDoc = client.upsert(doc, persistTo, replicateTo);
}
else if (failOnMissing) {
storedDoc = client.replace(doc, persistTo, replicateTo);
}
else {
storedDoc = client.insert(doc, persistTo, replicateTo);
}
if (storedDoc != null && storedDoc.cas() != 0) {
//inject new cas into the bean
beanWrapper.setProperty(versionProperty, storedDoc.cas());
return true;
}
return false;
}
catch (CASMismatchException e) {
throw new OptimisticLockingFailureException(operationDesc +
" document with version value failed: " + version);
}
catch (Exception e) {
handleWriteResultError(operationDesc + " document failed: " + e.getMessage(), e);
return false; //this could be skipped if WriteResultChecking.EXCEPTION
}
}
});
//TODO event afterSave
}
private void doRemove(final Object objectToRemove, final PersistTo persistTo, final ReplicateTo replicateTo) {
ensureNotIterable(objectToRemove);
//TODO event BeforeDelete
if (objectToRemove instanceof String) {
execute(new BucketCallback<Boolean>() {
@Override
public Boolean doInBucket() throws InterruptedException, ExecutionException {
RawJsonDocument deletedDoc = client.remove((String) objectToRemove, persistTo, replicateTo,
RawJsonDocument.class);
return deletedDoc != null;
}
});
//TODO event afterDelete
return;
}
final CouchbaseDocument converted = new CouchbaseDocument();
converter.write(objectToRemove, converted);
execute(new BucketCallback<Boolean>() {
@Override
public Boolean doInBucket() {
RawJsonDocument deletedDoc = client.remove(converted.getId(), persistTo, replicateTo
, RawJsonDocument.class);
return deletedDoc != null;
}
});
//TODO event afterDelete
}
private <T> T mapToEntity(String id, Document<String> data, Class<T> entityClass) {
if (data == null) {
return null;
}
final CouchbaseDocument converted = new CouchbaseDocument(id);
Object readEntity = converter.read(entityClass, (CouchbaseDocument) decodeAndUnwrap(data, converted));
final BeanWrapper<Object> beanWrapper = BeanWrapper.create(readEntity, converter.getConversionService());
CouchbasePersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(readEntity.getClass());
if (persistentEntity.hasVersionProperty()) {
beanWrapper.setProperty(persistentEntity.getVersionProperty(), data.cas());
}
return (T) readEntity;
}
@Override
public Bucket getCouchbaseBucket() {
return this.client;

View File

@@ -86,7 +86,7 @@ class ConverterRegistration {
}
/**
* Returns whether the source type is a Mongo simple one.
* Returns whether the source type is a Couchbase simple one.
*
* @return
*/
@@ -95,7 +95,7 @@ class ConverterRegistration {
}
/**
* Returns whether the target type is a Mongo simple one.
* Returns whether the target type is a Couchbase simple one.
*
* @return
*/
@@ -104,7 +104,7 @@ class ConverterRegistration {
}
/**
* Returns whether the given type is a type that Mongo can handle basically.
* Returns whether the given type is a type that Couchbase can handle basically.
*
* @param type
* @return

View File

@@ -64,7 +64,7 @@ public class JacksonTranslationService implements TranslationService, Initializi
* @return the encoded JSON String.
*/
@Override
public final Object encode(final CouchbaseStorable source) {
public final String encode(final CouchbaseStorable source) {
Writer writer = new StringWriter();
try {
@@ -125,7 +125,7 @@ public class JacksonTranslationService implements TranslationService, Initializi
* @return the decoded structure.
*/
@Override
public final CouchbaseStorable decode(final Object source, final CouchbaseStorable target) {
public final CouchbaseStorable decode(final String source, final CouchbaseStorable target) {
try {
JsonParser parser = factory.createParser((String) source);
while (parser.nextToken() != null) {

View File

@@ -27,12 +27,12 @@ import org.springframework.data.couchbase.core.mapping.CouchbaseStorable;
public interface TranslationService {
/**
* Encodes a {@link CouchbaseDocument} into the target format.
* Encodes a JSON String into the target format.
*
* @param source the source document to encode.
* @param source the source contents to encode.
* @return the encoded document representation.
*/
Object encode(CouchbaseStorable source);
String encode(CouchbaseStorable source);
/**
* Decodes the target format into a {@link CouchbaseDocument}
@@ -41,5 +41,5 @@ public interface TranslationService {
* @param target the target of the populated data.
* @return a properly populated document to work with.
*/
CouchbaseStorable decode(Object source, CouchbaseStorable target);
CouchbaseStorable decode(String source, CouchbaseStorable target);
}

View File

@@ -21,14 +21,17 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import com.couchbase.client.java.document.RawJsonDocument;
import com.couchbase.client.java.document.json.JsonArray;
import org.springframework.data.mapping.model.SimpleTypeHolder;
public abstract class CouchbaseSimpleTypes {
static {
Set<Class<?>> simpleTypes = new HashSet<Class<?>>();
simpleTypes.add(CouchbaseDocument.class);
simpleTypes.add(CouchbaseList.class);
simpleTypes.add(RawJsonDocument.class);
simpleTypes.add(JsonArray.class);
COUCHBASE_SIMPLE_TYPES = Collections.unmodifiableSet(simpleTypes);
}

View File

@@ -0,0 +1,67 @@
package org.springframework.data.couchbase;
import java.util.Collections;
import java.util.List;
import com.couchbase.client.java.env.CouchbaseEnvironment;
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.WriteResultChecking;
@Configuration
public class TestApplicationConfig extends AbstractCouchbaseConfiguration {
@Autowired
private Environment springEnv;
@Bean
public String couchbaseAdminUser() {
return springEnv.getProperty("couchbase.adminUser", "Administrator");
}
@Bean
public String couchbaseAdminPassword() {
return springEnv.getProperty("couchbase.adminUser", "password");
}
@Override
protected List<String> getBootstrapHosts() {
return Collections.singletonList(springEnv.getProperty("couchbase.host", "127.0.0.1"));
}
@Override
protected String getBucketName() {
return springEnv.getProperty("couchbase.bucket", "default");
}
@Override
protected String getBucketPassword() {
return springEnv.getProperty("couchbase.password", "");
}
//TODO maybe create the bucket if doesn't exist
@Override
protected CouchbaseEnvironment getEnvironment() {
return DefaultCouchbaseEnvironment.builder()
.connectTimeout(10000)
.kvTimeout(10000)
.queryTimeout(10000)
.viewTimeout(10000)
.build();
}
@Override
public CouchbaseTemplate couchbaseTemplate() throws Exception {
CouchbaseTemplate template = super.couchbaseTemplate();
template.setWriteResultChecking(WriteResultChecking.LOG);
return template;
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2013 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.data.couchbase.core;
import org.springframework.data.annotation.Id;
import org.springframework.data.couchbase.core.mapping.Field;
/**
* Test class for persisting and loading from {@link CouchbaseTemplate}.
*
* @author Michael Nitschinger
*/
public class Beer {
@Id
private final String id;
private String name;
@Field("is_active")
private boolean active = true;
public Beer(String id) {
this.id = id;
}
@Override
public String toString() {
return "Beer [id=" + id + ", name=" + name + ", active=" + active + "]";
}
public Beer setName(String name) {
this.name = name;
return this;
}
public String getName() {
return name;
}
public Beer setActive(boolean active) {
this.active = active;
return this;
}
public boolean getActive() {
return active;
}
public String getId() {
return id;
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2013 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.data.couchbase.core.convert.translation;
import static org.junit.Assert.assertEquals;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
/**
* Verifies the functionality of a {@link JacksonTranslationService}.
*
* @author Michael Nitschinger
*/
public class JacksonTranslationServiceTests {
private TranslationService service;
@Before
public void setup() {
service = new JacksonTranslationService();
}
@Test
public void shouldEncodeNonASCII() {
CouchbaseDocument doc = new CouchbaseDocument("key");
doc.put("language", "русский");
String expected = "{\"language\":\"русский\"}";
assertEquals(expected, service.encode(doc));
}
@Test
public void shouldDecodeNonASCII() {
String source = "{\"language\":\"русский\"}";
CouchbaseDocument target = new CouchbaseDocument();
service.decode(source, target);
assertEquals("русский", target.get("language"));
}
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright 2013 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.data.couchbase.core.mapping;
import static org.junit.Assert.assertEquals;
import java.lang.reflect.Field;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.util.ReflectionUtils;
/**
* Verifies the correct behavior of properties on persistable objects.
*
* @author Michael Nitschinger
*/
public class BasicCouchbasePersistentPropertyTests {
/**
* Holds the entity to test against (contains the properties).
*/
CouchbasePersistentEntity<Beer> entity;
/**
* Create an instance of the demo entity.
*/
@Before
public void setUp() {
entity = new BasicCouchbasePersistentEntity<Beer>(
ClassTypeInformation.from(Beer.class));
}
/**
* Verifies the name of the property without annotations.
*/
@Test
public void usesPropertyFieldName() {
Field field = ReflectionUtils.findField(Beer.class, "description");
assertEquals("description", getPropertyFor(field).getFieldName());
}
/**
* Verifies the name of the property with custom name annotation.
*/
@Test
public void usesAnnotatedFieldName() {
Field field = ReflectionUtils.findField(Beer.class, "name");
assertEquals("foobar", getPropertyFor(field).getFieldName());
}
/**
* Helper method to create a property out of the field.
*
* @param field the field to retrieve the properties from.
* @return the actual BasicCouchbasePersistentProperty instance.
*/
private CouchbasePersistentProperty getPropertyFor(Field field) {
return new BasicCouchbasePersistentProperty(field, null, entity,
new SimpleTypeHolder(), PropertyNameFieldNamingStrategy.INSTANCE);
}
/**
* Simple POJO to test attribute properties and annotations.
*/
public class Beer {
@Id
private String id;
@org.springframework.data.couchbase.core.mapping.Field("foobar")
String name;
String description;
public String getId() {
return id;
}
}
}

View File

@@ -0,0 +1,186 @@
/*
* Copyright 2013 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.data.couchbase.core.mapping;
import static org.junit.Assert.assertEquals;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.annotation.Id;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.couchbase.TestApplicationConfig;
import org.springframework.data.couchbase.core.convert.CustomConversions;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Tests to verify custom mapping logic.
*
* @author Michael Nitschinger
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestApplicationConfig.class)
public class CustomConvertersTests {
@Autowired
private MappingCouchbaseConverter converter;
@After
public void cleanup() {
converter.setCustomConversions(new CustomConversions(Collections.emptyList()));
}
@Test
public void shouldWriteWithCustomConverter() {
List<Object> converters = new ArrayList<Object>();
converters.add(DateToStringConverter.INSTANCE);
converter.setCustomConversions(new CustomConversions(converters));
converter.afterPropertiesSet();
Date date = new Date();
BlogPost post = new BlogPost();
post.created = date;
CouchbaseDocument doc = new CouchbaseDocument();
converter.write(post, doc);
assertEquals(date.toString(), doc.getPayload().get("created"));
}
@Test
public void shouldReadWithCustomConverter() {
List<Object> converters = new ArrayList<Object>();
converters.add(IntegerToStringConverter.INSTANCE);
converter.setCustomConversions(new CustomConversions(converters));
converter.afterPropertiesSet();
CouchbaseDocument doc = new CouchbaseDocument();
doc.getPayload().put("content", 10);
Counter loaded = converter.read(Counter.class, doc);
assertEquals("even", loaded.content);
}
@Test
public void shouldWriteConvertFullDocument() {
List<Object> converters = new ArrayList<Object>();
converters.add(BlogPostToCouchbaseDocumentConverter.INSTANCE);
converter.setCustomConversions(new CustomConversions(converters));
converter.afterPropertiesSet();
BlogPost post = new BlogPost();
post.id = "foobar";
post.title = "The Foo of the Bar";
CouchbaseDocument doc = new CouchbaseDocument();
converter.write(post, doc);
assertEquals("The Foo of the Bar", doc.getPayload().get("title"));
assertEquals("the_foo_of_the_bar", doc.getPayload().get("slug"));
}
@Test
public void shouldReadConvertFullDocument() {
List<Object> converters = new ArrayList<Object>();
converters.add(CouchbaseDocumentToBlogPostConverter.INSTANCE);
converter.setCustomConversions(new CustomConversions(converters));
converter.afterPropertiesSet();
CouchbaseDocument doc = new CouchbaseDocument();
doc.getPayload().put("title", "My Title");
BlogPost loaded = converter.read(BlogPost.class, doc);
assertEquals("modified", loaded.id);
assertEquals("My Title!!", loaded.title);
}
public static class BlogPost {
@Id
public String id = "key";
@Field
public Date created;
@Field
public String title;
}
public class Counter {
@Field
public String content;
}
public static enum IntegerToStringConverter implements Converter<Integer, String> {
INSTANCE;
@Override
public String convert(Integer source) {
return source % 2 == 0 ? "even" : "odd";
}
}
public static enum DateToStringConverter implements Converter<Date, String> {
INSTANCE;
public static Format FORMATTER = new SimpleDateFormat("yyyy HH");
@Override
public String convert(Date source) {
return source.toString();
}
}
@WritingConverter
public static enum BlogPostToCouchbaseDocumentConverter implements Converter<BlogPost, CouchbaseDocument> {
INSTANCE;
@Override
public CouchbaseDocument convert(BlogPost source) {
return new CouchbaseDocument()
.setId(source.id)
.put("title", source.title)
.put("slug", source.title.toLowerCase().replaceAll(" ", "_"));
}
}
@ReadingConverter
public static enum CouchbaseDocumentToBlogPostConverter implements Converter<CouchbaseDocument, BlogPost> {
INSTANCE;
@Override
public BlogPost convert(CouchbaseDocument source) {
BlogPost post = new BlogPost();
post.id = "modified";
post.title = source.getPayload().get("title") + "!!";
return post;
}
}
}

View File

@@ -0,0 +1,664 @@
/*
* Copyright 2013 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.data.couchbase.core.mapping;
import static org.junit.Assert.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.joda.time.LocalDateTime;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.annotation.Id;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.couchbase.TestApplicationConfig;
import org.springframework.data.couchbase.core.convert.CustomConversions;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Michael Nitschinger
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestApplicationConfig.class)
public class MappingCouchbaseConverterTests {
@Autowired
private MappingCouchbaseConverter converter;
@Test
public void shouldNotThrowNPE() {
CouchbaseDocument converted = new CouchbaseDocument();
converter.write(null, converted);
assertNull(converted.getId());
assertEquals(0, converted.getExpiration());
}
@Test(expected = MappingException.class)
public void doesNotAllowSimpleType1() {
converter.write("hello", new CouchbaseDocument());
}
@Test(expected = MappingException.class)
public void doesNotAllowSimpleType2() {
converter.write(true, new CouchbaseDocument());
}
@Test(expected = MappingException.class)
public void doesNotAllowSimpleType3() {
converter.write(42, new CouchbaseDocument());
}
@Test(expected = MappingException.class)
public void needsIDOnEntity() {
converter.write(new EntityWithoutID("foo"),
new CouchbaseDocument());
}
@Test
public void writesString() throws Exception {
CouchbaseDocument converted = new CouchbaseDocument();
StringEntity entity = new StringEntity("foobar");
converter.write(entity, converted);
Map<String, Object> result = converted.export();
assertEquals(entity.getClass().getName(), result.get("_class"));
assertEquals("foobar", result.get("attr0"));
assertEquals(BaseEntity.ID, converted.getId());
}
@Test
public void readsString() {
CouchbaseDocument source = new CouchbaseDocument();
source.put("_class", StringEntity.class.getName());
source.put("attr0", "foobar");
StringEntity converted = converter.read(StringEntity.class, source);
assertEquals("foobar", converted.attr0);
}
@Test
public void writesNumber() {
CouchbaseDocument converted = new CouchbaseDocument();
NumberEntity entity = new NumberEntity(42);
converter.write(entity, converted);
Map<String, Object> result = converted.export();
assertEquals(entity.getClass().getName(), result.get("_class"));
assertEquals(42L, result.get("attr0"));
assertEquals(BaseEntity.ID, converted.getId());
}
@Test
public void readsNumber() {
CouchbaseDocument source = new CouchbaseDocument();
source.put("_class", NumberEntity.class.getName());
source.put("attr0", 42);
NumberEntity converted = converter.read(NumberEntity.class, source);
assertEquals(42, converted.attr0);
}
@Test
public void writesBoolean() {
CouchbaseDocument converted = new CouchbaseDocument();
BooleanEntity entity = new BooleanEntity(true);
converter.write(entity, converted);
Map<String, Object> result = converted.export();
assertEquals(entity.getClass().getName(), result.get("_class"));
assertEquals(true, result.get("attr0"));
assertEquals("mockid", converted.getId());
}
@Test
public void readsBoolean() {
CouchbaseDocument source = new CouchbaseDocument();
source.put("_class", BooleanEntity.class.getName());
source.put("attr0", true);
BooleanEntity converted = converter.read(BooleanEntity.class, source);
assertTrue(converted.attr0);
}
@Test
public void writesMixedSimpleTypes() {
CouchbaseDocument converted = new CouchbaseDocument();
MixedSimpleEntity entity = new MixedSimpleEntity("a", 5, -0.3, true);
converter.write(entity, converted);
Map<String, Object> result = converted.export();
assertEquals(entity.getClass().getName(), result.get("_class"));
assertEquals("a", result.get("attr0"));
assertEquals(5, result.get("attr1"));
assertEquals(-0.3, result.get("attr2"));
assertEquals(true, result.get("attr3"));
}
@Test
public void readsMixedSimpleTypes() {
CouchbaseDocument source = new CouchbaseDocument();
source.put("_class", MixedSimpleEntity.class.getName());
source.put("attr0", "a");
source.put("attr1", 5);
source.put("attr2", -0.3);
source.put("attr3", true);
MixedSimpleEntity converted = converter.read(MixedSimpleEntity.class, source);
assertEquals("a", converted.attr0);
assertEquals(5, converted.attr1);
assertEquals(-0.3, converted.attr2, 0);
assertTrue(converted.attr3);
}
@Test
public void readsID() {
CouchbaseDocument document = new CouchbaseDocument("001");
BasicCouchbasePersistentPropertyTests.Beer beer = converter.read(BasicCouchbasePersistentPropertyTests.Beer.class,
document);
assertEquals("001", beer.getId());
}
@Test
public void writesUninitializedValues() {
CouchbaseDocument converted = new CouchbaseDocument();
UninitializedEntity entity = new UninitializedEntity();
converter.write(entity, converted);
Map<String, Object> result = converted.export();
assertEquals(entity.getClass().getName(), result.get("_class"));
assertEquals(0, result.get("attr1"));
}
@Test
public void readsUninitializedValues() {
CouchbaseDocument source = new CouchbaseDocument();
source.put("_class", UninitializedEntity.class.getName());
source.put("attr1", 0);
UninitializedEntity converted = converter.read(UninitializedEntity.class, source);
assertNull(converted.attr0);
assertEquals(0, converted.attr1);
assertNull(converted.attr2);
}
@Test
public void writesAndReadsMapsAndNestedMaps() {
CouchbaseDocument converted = new CouchbaseDocument();
Map<String, String> attr0 = new HashMap<String, String>();
Map<String, Boolean> attr1 = new TreeMap<String, Boolean>();
Map<Integer, String> attr2 = new LinkedHashMap<Integer, String>();
Map<String, Map<String, String>> attr3 =
new HashMap<String, Map<String, String>>();
attr0.put("foo", "bar");
attr1.put("bar", true);
attr3.put("hashmap", attr0);
MapEntity entity = new MapEntity(attr0, attr1, attr2, attr3);
converter.write(entity, converted);
Map<String, Object> result = converted.export();
assertEquals(attr0, result.get("attr0"));
assertEquals(attr1, result.get("attr1"));
assertEquals(attr2, result.get("attr2"));
assertEquals(attr3, result.get("attr3"));
CouchbaseDocument cattr0 = new CouchbaseDocument();
cattr0.put("foo", "bar");
CouchbaseDocument cattr1 = new CouchbaseDocument();
cattr1.put("bar", true);
CouchbaseDocument cattr2 = new CouchbaseDocument();
CouchbaseDocument cattr3 = new CouchbaseDocument();
cattr3.put("hashmap", cattr0);
CouchbaseDocument source = new CouchbaseDocument();
source.put("_class", MapEntity.class.getName());
source.put("attr0", cattr0);
source.put("attr1", cattr1);
source.put("attr2", cattr2);
source.put("attr3", cattr3);
MapEntity readConverted = converter.read(MapEntity.class, source);
assertEquals(attr0, readConverted.attr0);
assertEquals(attr1, readConverted.attr1);
assertEquals(attr2, readConverted.attr2);
assertEquals(attr3, readConverted.attr3);
}
@Test
public void writesAndReadsListAndNestedList() {
CouchbaseDocument converted = new CouchbaseDocument();
List<String> attr0 = new ArrayList<String>();
List<Integer> attr1 = new LinkedList<Integer>();
List<List<String>> attr2 = new ArrayList<List<String>>();
attr0.add("foo");
attr0.add("bar");
attr2.add(attr0);
ListEntity entity = new ListEntity(attr0, attr1, attr2);
converter.write(entity, converted);
Map<String, Object> result = converted.export();
assertEquals(attr0, result.get("attr0"));
assertEquals(attr1, result.get("attr1"));
assertEquals(attr2, result.get("attr2"));
CouchbaseDocument source = new CouchbaseDocument();
source.put("_class", ListEntity.class.getName());
CouchbaseList cattr0 = new CouchbaseList();
cattr0.put("foo");
cattr0.put("bar");
CouchbaseList cattr1 = new CouchbaseList();
CouchbaseList cattr2 = new CouchbaseList();
cattr2.put(cattr0);
source.put("attr0", cattr0);
source.put("attr1", cattr1);
source.put("attr2", cattr2);
ListEntity readConverted = converter.read(ListEntity.class, source);
assertEquals(2, readConverted.attr0.size());
assertEquals(0, readConverted.attr1.size());
assertEquals(1, readConverted.attr2.size());
assertEquals(2, readConverted.attr2.get(0).size());
}
@Test
public void writesAndReadsSetAndNestedSet() {
CouchbaseDocument converted = new CouchbaseDocument();
Set<String> attr0 = new HashSet<String>();
TreeSet<Integer> attr1 = new TreeSet<Integer>();
Set<Set<String>> attr2 = new HashSet<Set<String>>();
attr0.add("foo");
attr0.add("bar");
attr2.add(attr0);
SetEntity entity = new SetEntity(attr0, attr1, attr2);
converter.write(entity, converted);
Map<String, Object> result = converted.export();
assertEquals(attr0.size(), ((Collection) result.get("attr0")).size());
assertEquals(attr1.size(), ((Collection) result.get("attr1")).size());
assertEquals(attr2.size(), ((Collection) result.get("attr2")).size());
CouchbaseList cattr0 = new CouchbaseList();
cattr0.put("foo");
cattr0.put("bar");
CouchbaseList cattr1 = new CouchbaseList();
CouchbaseList cattr2 = new CouchbaseList();
cattr2.put(cattr0);
CouchbaseDocument source = new CouchbaseDocument();
source.put("_class", SetEntity.class.getName());
source.put("attr0", cattr0);
source.put("attr1", cattr1);
source.put("attr2", cattr2);
SetEntity readConverted = converter.read(SetEntity.class, source);
assertEquals(attr0, readConverted.attr0);
assertEquals(attr1, readConverted.attr1);
assertEquals(attr2, readConverted.attr2);
}
@Test
public void writesAndReadsValueClass() {
CouchbaseDocument converted = new CouchbaseDocument();
final String email = "foo@bar.com";
final Email addy = new Email(email);
List<Email> listOfEmails = new ArrayList<Email>();
listOfEmails.add(addy);
ValueEntity entity = new ValueEntity(addy, listOfEmails);
converter.write(entity, converted);
Map<String, Object> result = converted.export();
assertEquals(entity.getClass().getName(), result.get("_class"));
assertEquals(new HashMap<String, Object>() {{
put("emailAddr", email);
}}, result.get("email"));
CouchbaseDocument source = new CouchbaseDocument();
source.put("_class", ValueEntity.class.getName());
CouchbaseDocument emailDoc = new CouchbaseDocument();
emailDoc.put("emailAddr", "foo@bar.com");
source.put("email", emailDoc);
CouchbaseList listOfEmailsDoc = new CouchbaseList();
listOfEmailsDoc.put(emailDoc);
source.put("listOfEmails", listOfEmailsDoc);
ValueEntity readConverted = converter.read(ValueEntity.class, source);
assertEquals(addy.emailAddr, readConverted.email.emailAddr);
assertEquals(listOfEmails.get(0).emailAddr,
readConverted.listOfEmails.get(0).emailAddr);
}
@Test
public void writesAndReadsCustomConvertedClass() {
List<Object> converters = new ArrayList<Object>();
converters.add(BigDecimalToStringConverter.INSTANCE);
converters.add(StringToBigDecimalConverter.INSTANCE);
converter.setCustomConversions(new CustomConversions(converters));
converter.afterPropertiesSet();
CouchbaseDocument converted = new CouchbaseDocument();
final String valueStr = "12.345";
final BigDecimal value = new BigDecimal(valueStr);
final String value2Str = "0.6789";
final BigDecimal value2 = new BigDecimal(value2Str);
List<BigDecimal> listOfValues = new ArrayList<BigDecimal>();
listOfValues.add(value);
listOfValues.add(value2);
Map<String, BigDecimal> mapOfValues = new HashMap<String, BigDecimal>();
mapOfValues.put("val1", value);
mapOfValues.put("val2", value2);
CustomEntity entity = new CustomEntity(value, listOfValues, mapOfValues);
converter.write(entity, converted);
CouchbaseDocument source = new CouchbaseDocument();
source.put("_class", CustomEntity.class.getName());
source.put("value", valueStr);
CouchbaseList listOfValuesDoc = new CouchbaseList();
listOfValuesDoc.put(valueStr);
listOfValuesDoc.put(value2Str);
source.put("listOfValues", listOfValuesDoc);
CouchbaseDocument mapOfValuesDoc = new CouchbaseDocument();
mapOfValuesDoc.put("val1", valueStr);
mapOfValuesDoc.put("val2", value2Str);
source.put("mapOfValues", mapOfValuesDoc);
assertEquals(source.export().toString(), converted.export().toString());
CustomEntity readConverted = converter.read(CustomEntity.class, source);
assertEquals(value, readConverted.value);
assertEquals(listOfValues.get(0), readConverted.listOfValues.get(0));
assertEquals(listOfValues.get(1), readConverted.listOfValues.get(1));
assertEquals(mapOfValues.get("val1"), readConverted.mapOfValues.get("val1"));
assertEquals(mapOfValues.get("val2"), readConverted.mapOfValues.get("val2"));
}
@Test
public void writesAndReadsClassContainingCustomConvertedObjects() {
List<Object> converters = new ArrayList<Object>();
converters.add(BigDecimalToStringConverter.INSTANCE);
converters.add(StringToBigDecimalConverter.INSTANCE);
converter.setCustomConversions(new CustomConversions(converters));
converter.afterPropertiesSet();
CouchbaseDocument converted = new CouchbaseDocument();
final String weightStr = "12.34";
final BigDecimal weight = new BigDecimal(weightStr);
final CustomObject addy = new CustomObject(weight);
List<CustomObject> listOfObjects = new ArrayList<CustomObject>();
listOfObjects.add(addy);
Map<String, CustomObject> mapOfObjects = new HashMap<String, CustomObject>();
mapOfObjects.put("obj0", addy);
mapOfObjects.put("obj1", addy);
CustomObjectEntity entity = new CustomObjectEntity(addy, listOfObjects, mapOfObjects);
converter.write(entity, converted);
CouchbaseDocument source = new CouchbaseDocument();
source.put("_class", CustomObjectEntity.class.getName());
CouchbaseDocument objectDoc = new CouchbaseDocument();
objectDoc.put("weight", weightStr);
source.put("object", objectDoc);
CouchbaseList listOfObjectsDoc = new CouchbaseList();
listOfObjectsDoc.put(objectDoc);
source.put("listOfObjects", listOfObjectsDoc);
CouchbaseDocument mapOfObjectsDoc = new CouchbaseDocument();
mapOfObjectsDoc.put("obj0", objectDoc);
mapOfObjectsDoc.put("obj1", objectDoc);
source.put("mapOfObjects", mapOfObjectsDoc);
assertEquals(source.export().toString(), converted.export().toString());
CustomObjectEntity readConverted = converter.read(CustomObjectEntity.class, source);
assertEquals(addy.weight, readConverted.object.weight);
assertEquals(listOfObjects.get(0).weight, readConverted.listOfObjects.get(0).weight);
assertEquals(mapOfObjects.get("obj0").weight, readConverted.mapOfObjects.get("obj0").weight);
assertEquals(mapOfObjects.get("obj1").weight, readConverted.mapOfObjects.get("obj1").weight);
}
@Test
public void writesAndReadsDates() {
Date created = new Date();
Calendar modified = Calendar.getInstance();
LocalDateTime deleted = LocalDateTime.now();
DateEntity entity = new DateEntity(created, modified, deleted);
CouchbaseDocument converted = new CouchbaseDocument();
converter.write(entity, converted);
assertEquals(created.getTime(), converted.getPayload().get("created"));
assertEquals(modified.getTimeInMillis() / 1000, converted.getPayload().get("modified"));
assertEquals(deleted.toDate().getTime(), converted.getPayload().get("deleted"));
DateEntity read = converter.read(DateEntity.class, converted);
assertEquals(created.getTime(), read.created.getTime());
assertEquals(modified.getTimeInMillis() / 1000, read.modified.getTimeInMillis() / 1000);
assertEquals(deleted.toDate().getTime(), read.deleted.toDate().getTime());
}
static class EntityWithoutID {
private String attr0;
public EntityWithoutID(String a0) {
attr0 = a0;
}
}
static class BaseEntity {
public static final String ID = "mockid";
@Id
private String id = ID;
}
static class StringEntity extends BaseEntity {
private String attr0;
public StringEntity(String attr0) {
this.attr0 = attr0;
}
}
static class NumberEntity extends BaseEntity {
private long attr0;
public NumberEntity(long attr0) {
this.attr0 = attr0;
}
}
static class BooleanEntity extends BaseEntity {
private boolean attr0;
public BooleanEntity(boolean attr0) {
this.attr0 = attr0;
}
}
static class MixedSimpleEntity extends BaseEntity {
private String attr0;
private int attr1;
private double attr2;
private boolean attr3;
public MixedSimpleEntity(String attr0, int attr1, double attr2, boolean attr3) {
this.attr0 = attr0;
this.attr1 = attr1;
this.attr2 = attr2;
this.attr3 = attr3;
}
}
static class UninitializedEntity extends BaseEntity {
private String attr0 = null;
private int attr1;
private Integer attr2;
}
static class MapEntity extends BaseEntity {
private Map<String, String> attr0;
private Map<String, Boolean> attr1;
private Map<Integer, String> attr2;
private Map<String, Map<String, String>> attr3;
public MapEntity(Map<String, String> attr0, Map<String, Boolean> attr1, Map<Integer, String> attr2, Map<String, Map<String, String>> attr3) {
this.attr0 = attr0;
this.attr1 = attr1;
this.attr2 = attr2;
this.attr3 = attr3;
}
}
static class ListEntity extends BaseEntity {
private List<String> attr0;
private List<Integer> attr1;
private List<List<String>> attr2;
ListEntity(List<String> attr0, List<Integer> attr1, List<List<String>> attr2) {
this.attr0 = attr0;
this.attr1 = attr1;
this.attr2 = attr2;
}
}
static class SetEntity extends BaseEntity {
private Set<String> attr0;
private Set<Integer> attr1;
private Set<Set<String>> attr2;
SetEntity(Set<String> attr0, Set<Integer> attr1, Set<Set<String>> attr2) {
this.attr0 = attr0;
this.attr1 = attr1;
this.attr2 = attr2;
}
}
static class ValueEntity extends BaseEntity {
private Email email;
private List<Email> listOfEmails;
public ValueEntity(Email email, List<Email> listOfEmails) {
this.email = email;
this.listOfEmails = listOfEmails;
}
}
static class Email {
private String emailAddr;
public Email(String emailAddr) {
this.emailAddr = emailAddr;
}
}
static class CustomEntity extends BaseEntity {
private BigDecimal value;
private List<BigDecimal> listOfValues;
private Map<String, BigDecimal> mapOfValues;
public CustomEntity(BigDecimal value, List<BigDecimal> listOfValues, Map<String, BigDecimal> mapOfValues) {
this.value = value;
this.listOfValues = listOfValues;
this.mapOfValues = mapOfValues;
}
}
static class CustomObjectEntity extends BaseEntity {
private CustomObject object;
private List<CustomObject> listOfObjects;
private Map<String, CustomObject> mapOfObjects;
public CustomObjectEntity(CustomObject object, List<CustomObject> listOfObjects, Map<String, CustomObject> mapOfObjects) {
this.object = object;
this.listOfObjects = listOfObjects;
this.mapOfObjects = mapOfObjects;
}
}
static class CustomObject {
private BigDecimal weight;
public CustomObject(BigDecimal weight) {
this.weight = weight;
}
}
@WritingConverter
public static enum BigDecimalToStringConverter implements Converter<BigDecimal, String> {
INSTANCE;
@Override
public String convert(BigDecimal source) {
return source.toPlainString();
}
}
@ReadingConverter
public static enum StringToBigDecimalConverter implements Converter<String, BigDecimal> {
INSTANCE;
@Override
public BigDecimal convert(String source) {
return new BigDecimal(source);
}
}
static class DateEntity extends BaseEntity {
private Date created;
private Calendar modified;
private LocalDateTime deleted;
public DateEntity(Date created, Calendar modified, LocalDateTime deleted) {
this.created = created;
this.modified = modified;
this.deleted = deleted;
}
}
}