DATACOUCH-275 Key auto generation using attributes, uuid
Original pull request: #142.
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
package org.springframework.data.couchbase;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class IntegrationTestCustomKeySettings extends IntegrationTestApplicationConfig {
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package org.springframework.data.couchbase.core;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.data.couchbase.core.mapping.id.GenerationStrategy.*;
|
||||
|
||||
import com.couchbase.client.java.Bucket;
|
||||
import com.couchbase.client.java.error.DocumentDoesNotExistException;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TestName;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
import org.springframework.data.couchbase.core.mapping.id.GeneratedValue;
|
||||
import org.springframework.data.couchbase.core.mapping.id.IdAttribute;
|
||||
import org.springframework.data.couchbase.core.mapping.id.IdPrefix;
|
||||
import org.springframework.data.couchbase.core.mapping.id.IdSuffix;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = IntegrationTestApplicationConfig.class)
|
||||
public class CouchbaseTemplateIdGenerationTests {
|
||||
|
||||
@Rule
|
||||
public TestName testName = new TestName();
|
||||
|
||||
@Autowired
|
||||
private Bucket client;
|
||||
|
||||
@Autowired
|
||||
private CouchbaseTemplate template;
|
||||
|
||||
|
||||
private void removeIfExist(String key) {
|
||||
try {
|
||||
client.remove(key);
|
||||
}
|
||||
catch (DocumentDoesNotExistException e) {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGenerateIdUsingAtrributes() throws Exception {
|
||||
SimpleClassWithGeneratedIdValueUsingAttributes simpleClass = new SimpleClassWithGeneratedIdValueUsingAttributes();
|
||||
String generatedId = template.getGeneratedId(simpleClass);
|
||||
|
||||
removeIfExist(generatedId);
|
||||
assertEquals("Id generation should be correct", generatedId,
|
||||
"prefix1::prefix2::0::1::2.0::3.0::4::Simple::Nested{value:simple}::suffix1::suffix2");
|
||||
template.insert(simpleClass);
|
||||
assertEquals("Exists after insert", true, template.exists(generatedId));
|
||||
simpleClass.value = "modified";
|
||||
template.save(simpleClass);
|
||||
SimpleClassWithGeneratedIdValueUsingAttributes modifiedClass = template.findById(generatedId,
|
||||
SimpleClassWithGeneratedIdValueUsingAttributes.class);
|
||||
assertEquals("Get after save id should be correct", generatedId, modifiedClass.id);
|
||||
template.update(simpleClass);
|
||||
SimpleClassWithGeneratedIdValueUsingAttributes updatedClass = template.findById(generatedId,
|
||||
SimpleClassWithGeneratedIdValueUsingAttributes.class);
|
||||
assertEquals("Get after update id should be correct", generatedId, updatedClass.id);
|
||||
template.remove(generatedId);
|
||||
assertEquals("Exists after remove", false, template.exists(generatedId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGenerateIdUsingUUID() throws Exception {
|
||||
SimpleClassWithGeneratedIdValueUsingUUID simpleClass = new SimpleClassWithGeneratedIdValueUsingUUID();
|
||||
String generatedId = template.getGeneratedId(simpleClass);
|
||||
simpleClass.id = generatedId;
|
||||
template.insert(simpleClass);
|
||||
assertEquals("Should not regenerate id", generatedId, simpleClass.id);
|
||||
template.remove(generatedId);
|
||||
assertEquals("Exists after remove", false, template.exists(generatedId));
|
||||
}
|
||||
|
||||
|
||||
@Document
|
||||
static class SimpleClassWithGeneratedIdValueUsingAttributes {
|
||||
|
||||
@Id @GeneratedValue(strategy = USE_ATTRIBUTES, delimiter = "::")
|
||||
public String id;
|
||||
|
||||
@IdAttribute(order = 6)
|
||||
public Nested nested = new Nested("simple");
|
||||
|
||||
@IdAttribute(order = 5)
|
||||
public String type = "Simple";
|
||||
|
||||
@IdAttribute(order = 4)
|
||||
public int intNum = 4;
|
||||
|
||||
@IdAttribute(order = 2)
|
||||
public float floatNum = 2F;
|
||||
|
||||
@IdAttribute(order = 3)
|
||||
public double doubleNum = 3;
|
||||
|
||||
@IdAttribute
|
||||
public long longNum = 0L;
|
||||
|
||||
@IdAttribute(order = 1)
|
||||
public short shortNum = 1;
|
||||
|
||||
@IdPrefix(order = 1)
|
||||
public String prefix2 = "prefix2";
|
||||
|
||||
@IdPrefix
|
||||
public String prefix1 = "prefix1";
|
||||
|
||||
@IdSuffix(order = 1)
|
||||
public String suffix2 = "suffix2";
|
||||
|
||||
@IdSuffix
|
||||
public String suffix1 = "suffix1";
|
||||
|
||||
public String value = "new";
|
||||
|
||||
}
|
||||
|
||||
static class Nested {
|
||||
private String value;
|
||||
|
||||
public Nested(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Nested{value:" + value + "}";
|
||||
}
|
||||
}
|
||||
|
||||
@Document
|
||||
static class SimpleClassWithGeneratedIdValueUsingUUID {
|
||||
@Id @GeneratedValue(strategy = UNIQUE)
|
||||
public String id;
|
||||
|
||||
public String value = "new";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.springframework.data.couchbase.core;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import com.couchbase.client.java.Bucket;
|
||||
import com.couchbase.client.java.error.DocumentDoesNotExistException;
|
||||
import com.couchbase.client.java.repository.annotation.Id;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TestName;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.couchbase.IntegrationTestCustomKeySettings;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
import org.springframework.data.couchbase.core.mapping.KeySettings;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = IntegrationTestCustomKeySettings.class)
|
||||
public class CouchbaseTemplateKeySettingsTests {
|
||||
|
||||
@Rule
|
||||
public TestName testName = new TestName();
|
||||
|
||||
@Autowired
|
||||
private CouchbaseTemplate template;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
if (template.keySettings() == null) {
|
||||
template.keySettings(KeySettings.build().prefix("MyAppPrefix").suffix("MyAppSuffix").delimiter("::"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAddCustomKeySettings() throws Exception {
|
||||
SimpleClass simpleClass = new SimpleClass();
|
||||
String generatedId = template.getGeneratedId(simpleClass);
|
||||
assertEquals("Id generated should include custom key settings", "MyAppPrefix::myId::MyAppSuffix", generatedId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotAllowKeySettingsToBeChanged() {
|
||||
try {
|
||||
template.keySettings(KeySettings.build().prefix("MyAppPrefix").suffix("MyAppSuffix").delimiter("::"));
|
||||
fail("excepted unsupportedOperationException");
|
||||
} catch(Exception ex) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Document
|
||||
static class SimpleClass {
|
||||
@Id
|
||||
public String id = "myId";
|
||||
}
|
||||
}
|
||||
106
src/main/asciidoc/autokeygeneration.adoc
Normal file
106
src/main/asciidoc/autokeygeneration.adoc
Normal file
@@ -0,0 +1,106 @@
|
||||
[[couchbase.autokeygeneration]]
|
||||
= Auto generating keys
|
||||
|
||||
This chapter describes how couchbase document keys can be auto-generated using builtin mechanisms.
|
||||
There are two types of auto-generation strategies supported.
|
||||
|
||||
- <<couchbase.autokeygeneration.usingattributes>>
|
||||
- <<couchbase.autokeygeneration.unique>>
|
||||
|
||||
NOTE: The maximum key length supported by couchbase is 250 bytes.
|
||||
|
||||
[[couchbase.autokeygeneration.configuration]]
|
||||
== Configuration
|
||||
|
||||
Keys to be auto-generated should be annotated with `@GeneratedValue`. The default strategy is `USE_ATTRIBUTES`. Prefix
|
||||
and suffix for the key can be provided as part of the entity itself, these values are not persisted, they are only
|
||||
used for key generation. The prefixes and suffixes are ordered using the `order` value. The default order is `0`, multiple
|
||||
prefixes without order will overwrite the previous. If a value for id is already available, auto-generation will be skipped.
|
||||
The delimiter for concatenation can be provided using `delimiter`, the default delimiter is `.`.
|
||||
|
||||
.Annotation for GeneratedValue
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Document
|
||||
public class User {
|
||||
@Id @GeneratedValue(strategy = USE_ATTRIBUTES, delimiter = ".")
|
||||
private String id;
|
||||
@IdPrefix(order=0)
|
||||
private String userPrefix;
|
||||
@IdSuffix(order=0)
|
||||
private String userSuffix;
|
||||
...
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Common prefix and suffix for all entities keys can be added to `CouchbaseTemplate` directly. Once added to the `CouchbaseTemplate`,
|
||||
they become immutable. These settings are always applied irrespective of the `GeneratedValue` annotation.
|
||||
|
||||
.Common key settings in CouchbaseTemplate
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Autowired
|
||||
CouchbaseTemplate couchbaseTemplate;
|
||||
...
|
||||
couchbaseTemplate.keySettings(KeySettings.build().prefix("ApplicationA").suffix("Server1").delimiter("::"));
|
||||
----
|
||||
====
|
||||
|
||||
Key will be auto-generated only for operations with direct entity input like insert, update, save, delete using entity.
|
||||
For other operations requiring just the key, it can be generated using `CouchbaseTemplate`.
|
||||
|
||||
.Standalone key generation in CouchbaseTemplate
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Autowired
|
||||
CouchbaseTemplate couchbaseTemplate;
|
||||
...
|
||||
String id = couchbaseTemplate.getGeneratedId(entity);
|
||||
...
|
||||
repo.exists(id);
|
||||
----
|
||||
====
|
||||
|
||||
[[couchbase.autokeygeneration.usingattributes]]
|
||||
== Key generation using attributes
|
||||
|
||||
It is a common practice to generate keys using a combination of the document attributes. Key generation using attributes
|
||||
concatenates all the attribute values annotated with `IdAttribute`, based on the ordering provided similar to prefixes and suffixes.
|
||||
|
||||
.Annotation for IdAttribute
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Document
|
||||
public class User {
|
||||
@Id @GeneratedValue(strategy = USE_ATTRIBUTES)
|
||||
private String id;
|
||||
@IdAttribute
|
||||
private String userid;
|
||||
...
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[[couchbase.autokeygeneration.unique]]
|
||||
== Key generation using uuid
|
||||
|
||||
This auto-generation uses UUID random generator to generate document keys consuming 16 bytes of key space. This mechanism
|
||||
is only recommended for test scaffolding.
|
||||
|
||||
.Annotation for Unique key generation
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Document
|
||||
public class User {
|
||||
@Id @GeneratedValue(strategy = UNIQUE)
|
||||
private String id;
|
||||
...
|
||||
}
|
||||
----
|
||||
====
|
||||
@@ -20,6 +20,7 @@ include::preface.adoc[]
|
||||
:leveloffset: +1
|
||||
include::configuration.adoc[]
|
||||
include::entity.adoc[]
|
||||
include::autokeygeneration.adoc[]
|
||||
include::{spring-data-commons-docs}/repositories.adoc[]
|
||||
include::repository.adoc[]
|
||||
include::template.adoc[]
|
||||
|
||||
@@ -35,6 +35,7 @@ import com.couchbase.client.java.view.ViewResult;
|
||||
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
|
||||
import org.springframework.data.couchbase.core.mapping.KeySettings;
|
||||
import org.springframework.data.couchbase.core.query.Consistency;
|
||||
|
||||
/**
|
||||
@@ -389,4 +390,31 @@ public interface CouchbaseOperations {
|
||||
* @return the consistency to use for generated repository queries.
|
||||
*/
|
||||
Consistency getDefaultConsistency();
|
||||
|
||||
/**
|
||||
* Add common key settings
|
||||
*
|
||||
* Throws {@link UnsupportedOperationException} if KeySettings is already set. It becomes immutable.
|
||||
*
|
||||
* @param settings {@link KeySettings}
|
||||
*/
|
||||
void keySettings(final KeySettings settings);
|
||||
|
||||
/**
|
||||
* Get key settings associated with the template
|
||||
*
|
||||
* @return {@link KeySettings}
|
||||
*/
|
||||
KeySettings keySettings();
|
||||
|
||||
/**
|
||||
* Get generated id - applies both using prefix and suffix through entity as well as template {@link KeySettings}
|
||||
*
|
||||
* ** NOTE: UNIQUE strategy will generate different ids each time ***
|
||||
*
|
||||
* @param entity the entity object.
|
||||
* @return id the couchbase document key.
|
||||
*/
|
||||
String getGeneratedId(Object entity);
|
||||
|
||||
}
|
||||
@@ -49,6 +49,12 @@ import com.couchbase.client.java.view.ViewQuery;
|
||||
import com.couchbase.client.java.view.ViewResult;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
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.couchbase.core.mapping.KeySettings;
|
||||
import rx.Observable;
|
||||
import rx.functions.Func1;
|
||||
|
||||
@@ -61,11 +67,6 @@ 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.couchbase.core.mapping.event.AfterDeleteEvent;
|
||||
import org.springframework.data.couchbase.core.mapping.event.AfterSaveEvent;
|
||||
import org.springframework.data.couchbase.core.mapping.event.BeforeConvertEvent;
|
||||
@@ -101,6 +102,7 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
|
||||
private final CouchbaseConverter converter;
|
||||
private final TranslationService translationService;
|
||||
private final ClusterInfo clusterInfo;
|
||||
private KeySettings keySettings;
|
||||
|
||||
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
@@ -120,7 +122,8 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
|
||||
this(clusterInfo, client, null, translationService);
|
||||
}
|
||||
|
||||
public CouchbaseTemplate(final ClusterInfo clusterInfo, final Bucket client,
|
||||
public CouchbaseTemplate(final ClusterInfo clusterInfo,
|
||||
final Bucket client,
|
||||
final CouchbaseConverter converter,
|
||||
final TranslationService translationService) {
|
||||
this.clusterInfo = clusterInfo;
|
||||
@@ -128,8 +131,10 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
|
||||
this.converter = converter == null ? getDefaultConverter() : converter;
|
||||
this.translationService = translationService == null ? getDefaultTranslationService() : translationService;
|
||||
this.mappingContext = this.converter.getMappingContext();
|
||||
|
||||
}
|
||||
|
||||
|
||||
private TranslationService getDefaultTranslationService() {
|
||||
JacksonTranslationService t = new JacksonTranslationService();
|
||||
t.afterPropertiesSet();
|
||||
@@ -581,12 +586,14 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
|
||||
execute(new BucketCallback<Boolean>() {
|
||||
@Override
|
||||
public Boolean doInBucket() throws InterruptedException, ExecutionException {
|
||||
converted.setId(addCommonPrefixAndSuffix(converted.getId()));
|
||||
Document<String> doc = encodeAndWrap(converted, version);
|
||||
Document<String> storedDoc;
|
||||
//We will check version only if required
|
||||
boolean versionPresent = versionProperty != null;
|
||||
//If version is not set - assumption that document is new, otherwise updating
|
||||
boolean existingDocument = version != null && version > 0L;
|
||||
|
||||
try {
|
||||
switch (persistType) {
|
||||
case SAVE:
|
||||
@@ -640,8 +647,7 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
|
||||
@Override
|
||||
public Boolean doInBucket() throws InterruptedException, ExecutionException {
|
||||
try {
|
||||
RawJsonDocument deletedDoc = client.remove((String) objectToRemove, persistTo, replicateTo,
|
||||
RawJsonDocument.class);
|
||||
RawJsonDocument deletedDoc = client.remove((String) objectToRemove , persistTo, replicateTo, RawJsonDocument.class);
|
||||
return deletedDoc != null;
|
||||
} catch (Exception e) {
|
||||
handleWriteResultError("Delete document failed: " + e.getMessage(), e);
|
||||
@@ -660,7 +666,7 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
|
||||
@Override
|
||||
public Boolean doInBucket() {
|
||||
try {
|
||||
RawJsonDocument deletedDoc = client.remove(converted.getId(), persistTo, replicateTo
|
||||
RawJsonDocument deletedDoc = client.remove(addCommonPrefixAndSuffix(converted.getId()), persistTo, replicateTo
|
||||
, RawJsonDocument.class);
|
||||
return deletedDoc != null;
|
||||
} catch (Exception e) {
|
||||
@@ -748,4 +754,42 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
|
||||
return springDataOperationName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keySettings(KeySettings settings) {
|
||||
if (this.keySettings != null) {
|
||||
throw new UnsupportedOperationException("Key settings is already set, it is no longer mutable");
|
||||
}
|
||||
this.keySettings = settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeySettings keySettings() {
|
||||
return this.keySettings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGeneratedId(Object entity) {
|
||||
ensureNotIterable(entity);
|
||||
CouchbaseDocument converted = new CouchbaseDocument();
|
||||
converter.write(entity, converted);
|
||||
return addCommonPrefixAndSuffix(converted.getId());
|
||||
}
|
||||
|
||||
private String addCommonPrefixAndSuffix(final String id) {
|
||||
String convertedKey = id;
|
||||
if (this.keySettings == null) {
|
||||
return id;
|
||||
}
|
||||
String prefix = this.keySettings.prefix();
|
||||
String delimiter = this.keySettings.delimiter();
|
||||
String suffix = this.keySettings.suffix();
|
||||
if (prefix != null && !prefix.equals("")) {
|
||||
convertedKey = prefix + delimiter + convertedKey;
|
||||
}
|
||||
if (suffix != null && !suffix.equals("")) {
|
||||
convertedKey = convertedKey + delimiter + suffix;
|
||||
}
|
||||
return convertedKey;
|
||||
}
|
||||
}
|
||||
@@ -16,12 +16,15 @@
|
||||
|
||||
package org.springframework.data.couchbase.core.convert;
|
||||
|
||||
import static org.springframework.data.couchbase.core.mapping.id.GenerationStrategy.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.couchbase.client.java.repository.annotation.Field;
|
||||
|
||||
@@ -35,6 +38,10 @@ import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseList;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||
import org.springframework.data.couchbase.core.mapping.id.GeneratedValue;
|
||||
import org.springframework.data.couchbase.core.mapping.id.IdAttribute;
|
||||
import org.springframework.data.couchbase.core.mapping.id.IdPrefix;
|
||||
import org.springframework.data.couchbase.core.mapping.id.IdSuffix;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.AssociationHandler;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
@@ -229,7 +236,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter
|
||||
entity.doWithProperties(new PropertyHandler<CouchbasePersistentProperty>() {
|
||||
@Override
|
||||
public void doWithPersistentProperty(final CouchbasePersistentProperty prop) {
|
||||
if (!doesPropertyExistInSource(prop) || entity.isConstructorArgument(prop)) {
|
||||
if (!doesPropertyExistInSource(prop) || entity.isConstructorArgument(prop) || isIdConstructionProperty(prop)) {
|
||||
return;
|
||||
}
|
||||
Object obj = prop.isIdProperty() ? source.getId() : getValueInternal(prop, source, instance);
|
||||
@@ -239,6 +246,10 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter
|
||||
private boolean doesPropertyExistInSource(final CouchbasePersistentProperty property) {
|
||||
return property.isIdProperty() || source.containsKey(property.getFieldName());
|
||||
}
|
||||
|
||||
private boolean isIdConstructionProperty(final CouchbasePersistentProperty property) {
|
||||
return property.isAnnotationPresent(IdPrefix.class) || property.isAnnotationPresent(IdSuffix.class);
|
||||
}
|
||||
});
|
||||
|
||||
entity.doWithAssociations(new AssociationHandler<CouchbasePersistentProperty>() {
|
||||
@@ -374,7 +385,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter
|
||||
|
||||
writeInternal(source, target, type);
|
||||
if (target.getId() == null) {
|
||||
throw new MappingException("An ID property is needed, but not found on this entity.");
|
||||
throw new MappingException("An ID property is needed, but not found/could not be generated on this entity.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -446,10 +457,11 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter
|
||||
final CouchbasePersistentProperty idProperty = entity.getIdProperty();
|
||||
final CouchbasePersistentProperty versionProperty = entity.getVersionProperty();
|
||||
|
||||
if (idProperty != null && target.getId() == null) {
|
||||
String id = accessor.getProperty(idProperty, String.class);
|
||||
target.setId(id);
|
||||
}
|
||||
GeneratedValue generatedValueInfo = null;
|
||||
final TreeMap<Integer, String> prefixes = new TreeMap<Integer, String>();
|
||||
final TreeMap<Integer, String> suffixes = new TreeMap<Integer, String>();
|
||||
final TreeMap<Integer, String> idAttributes = new TreeMap<Integer, String>();
|
||||
|
||||
target.setExpiration(entity.getExpiry());
|
||||
|
||||
entity.doWithProperties(new PropertyHandler<CouchbasePersistentProperty>() {
|
||||
@@ -457,12 +469,32 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter
|
||||
public void doWithPersistentProperty(final CouchbasePersistentProperty prop) {
|
||||
if (prop.equals(idProperty) || (versionProperty != null && prop.equals(versionProperty))) {
|
||||
return;
|
||||
} else if(enableStrictFieldChecking && !prop.isAnnotationPresent(Field.class)){
|
||||
} else if (enableStrictFieldChecking && !prop.isAnnotationPresent(Field.class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object propertyObj = accessor.getProperty(prop, prop.getType());
|
||||
if (null != propertyObj) {
|
||||
if (prop.isAnnotationPresent(IdPrefix.class)) {
|
||||
IdPrefix prefix = prop.findAnnotation(IdPrefix.class);
|
||||
int order = prefix.order();
|
||||
prefixes.put(order, convertToString(propertyObj));
|
||||
return;
|
||||
}
|
||||
|
||||
if (prop.isAnnotationPresent(IdSuffix.class)) {
|
||||
IdSuffix suffix = prop.findAnnotation(IdSuffix.class);
|
||||
int order = suffix.order();
|
||||
suffixes.put(order, convertToString(propertyObj));
|
||||
return;
|
||||
}
|
||||
|
||||
if (prop.isAnnotationPresent(IdAttribute.class)) {
|
||||
IdAttribute idAttribute = prop.findAnnotation(IdAttribute.class);
|
||||
int order = idAttribute.order();
|
||||
idAttributes.put(order, convertToString(propertyObj));
|
||||
}
|
||||
|
||||
if (!conversions.isSimpleType(propertyObj.getClass())) {
|
||||
writePropertyInternal(propertyObj, target, prop);
|
||||
} else {
|
||||
@@ -470,8 +502,29 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String convertToString(Object propertyObj) {
|
||||
if (propertyObj instanceof String) {
|
||||
return (String) propertyObj;
|
||||
} else if (propertyObj instanceof Number) {
|
||||
return new StringBuffer().append(propertyObj).toString();
|
||||
} else {
|
||||
return propertyObj.toString();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if (idProperty != null && target.getId() == null) {
|
||||
String id = accessor.getProperty(idProperty, String.class);
|
||||
if(idProperty.isAnnotationPresent(GeneratedValue.class) && (id == null || id.equals(""))) {
|
||||
generatedValueInfo = idProperty.findAnnotation(GeneratedValue.class);
|
||||
target.setId(generateId(generatedValueInfo, prefixes, suffixes, idAttributes));
|
||||
} else {
|
||||
target.setId(id);
|
||||
}
|
||||
}
|
||||
|
||||
entity.doWithAssociations(new AssociationHandler<CouchbasePersistentProperty>() {
|
||||
@Override
|
||||
public void doWithAssociation(final Association<CouchbasePersistentProperty> association) {
|
||||
@@ -836,4 +889,49 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter
|
||||
}
|
||||
}
|
||||
|
||||
private String generateId(GeneratedValue generatedValue, TreeMap<Integer, String> prefixes, TreeMap<Integer, String> suffixes,
|
||||
TreeMap<Integer, String> idAttributes) {
|
||||
String delimiter = generatedValue.delimiter();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean isAppending = false;
|
||||
if (prefixes.size() > 0) {
|
||||
appendKeyParts(sb, prefixes.values(), delimiter);
|
||||
isAppending = true;
|
||||
}
|
||||
|
||||
if (generatedValue.strategy() == USE_ATTRIBUTES && idAttributes.size() > 0) {
|
||||
if(isAppending) {
|
||||
sb.append(delimiter);
|
||||
}
|
||||
appendKeyParts(sb, idAttributes.values(), delimiter);
|
||||
}
|
||||
|
||||
if (generatedValue.strategy() == UNIQUE) {
|
||||
if(isAppending) {
|
||||
sb.append(delimiter);
|
||||
}
|
||||
sb.append(UUID.randomUUID());
|
||||
}
|
||||
|
||||
if (suffixes.size() > 0) {
|
||||
if(isAppending) {
|
||||
sb.append(delimiter);
|
||||
}
|
||||
appendKeyParts(sb, suffixes.values(), delimiter);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private StringBuilder appendKeyParts(StringBuilder sb, Collection<String> values, String delimiter) {
|
||||
boolean isAppending = false;
|
||||
for(String value : values) {
|
||||
if (isAppending) {
|
||||
sb.append(delimiter);
|
||||
} else {
|
||||
isAppending = true;
|
||||
}
|
||||
sb.append(value);
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2017 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;
|
||||
|
||||
/**
|
||||
* Common settings for Couchbase key
|
||||
* - prefix
|
||||
* - suffix
|
||||
* - delimiter
|
||||
*
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
public class KeySettings {
|
||||
|
||||
private static String DEFAULT_DELIMITER = ".";
|
||||
|
||||
private String commonPrefix;
|
||||
|
||||
private String commonSuffix;
|
||||
|
||||
private String delimiter;
|
||||
|
||||
public static KeySettings build() {
|
||||
return new KeySettings();
|
||||
}
|
||||
|
||||
protected KeySettings() {
|
||||
this.delimiter = DEFAULT_DELIMITER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set common prefix
|
||||
*/
|
||||
public KeySettings prefix(String prefix) {
|
||||
this.commonPrefix = prefix;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set common suffix
|
||||
*/
|
||||
public KeySettings suffix(String suffix) {
|
||||
this.commonSuffix = suffix;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set delimiter
|
||||
*/
|
||||
public KeySettings delimiter(String delimiter) {
|
||||
if (delimiter == null) {
|
||||
return this;
|
||||
}
|
||||
this.delimiter = delimiter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get common prefix
|
||||
*/
|
||||
public String prefix() {
|
||||
return this.commonPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get common suffix
|
||||
*/
|
||||
public String suffix() {
|
||||
return this.commonSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get delimiter
|
||||
*/
|
||||
public String delimiter() {
|
||||
return this.delimiter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2017 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.id;
|
||||
import static org.springframework.data.couchbase.core.mapping.id.GenerationStrategy.*;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
|
||||
/**
|
||||
* This annotation is targeted at {@link Document} interfaces, indicating that
|
||||
* the framework should generate the document key value using the specified generator
|
||||
*
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface GeneratedValue {
|
||||
|
||||
String delimiter() default ".";
|
||||
|
||||
GenerationStrategy strategy() default USE_ATTRIBUTES;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2017 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.id;
|
||||
|
||||
/**
|
||||
* Keys generation strategy
|
||||
*
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
public enum GenerationStrategy {
|
||||
/**
|
||||
* Constructs key from the entity attributes using the supplied id, prefix, suffix
|
||||
*/
|
||||
USE_ATTRIBUTES,
|
||||
|
||||
/**
|
||||
* Uses an uuid generator
|
||||
*/
|
||||
UNIQUE
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2017 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.id;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.annotation.Inherited;
|
||||
|
||||
/**
|
||||
* This annotation is targeted at building the document id using the attribute value.
|
||||
*
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
@Inherited
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface IdAttribute {
|
||||
int order() default 0;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2017 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.id;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.annotation.Inherited;
|
||||
|
||||
/**
|
||||
* This annotation is targeted at building the document id using the value
|
||||
* as a part of a prefix build. The order determines in way which the specified
|
||||
* prefix value is used in building the prefix.
|
||||
*
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
@Inherited
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface IdPrefix {
|
||||
int order() default 0;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2017 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.id;
|
||||
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.annotation.Inherited;
|
||||
|
||||
/**
|
||||
* This annotation is targeted at building the document id using the value
|
||||
* as a part of a suffix build. The order determines in way which the specified
|
||||
* suffix value is used in building the suffix.
|
||||
*
|
||||
* @author Subhashni Balakrishnan
|
||||
*/
|
||||
@Inherited
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface IdSuffix {
|
||||
int order() default 0;
|
||||
}
|
||||
Reference in New Issue
Block a user