implement template using SDK 2.x + tests
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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<String>} 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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user