DATACOUCH-525 - support custom type field name and custome type field value
MOTIVATION customers would like to specify a type field other than _class, and would like to specify a type field value other than the class name of the domain object CHANGES Follow the instructions at https://stackoverflow.com/questions/38847605/how-to-use-spring-data-with-couchbase-without-class-attribute in the test domain, supply a CustomMappingCouchbaseConvert class that leverates a TypeBasedCouchbaseTypeMapper, which in turn uses a TypeAwareTypeInformationMapper. The typeKey used is "t". The TypeAwareTypeInformationMapper uses the DocumentType annotation as the value for the type map entry. The domain Person object has @DocumentType("p"), which will result in type mapping of "t" : "p" for Person objects. In addition, when a query is composed, the typeKey and typeValue are taken from the converter.
This commit is contained in:
@@ -21,6 +21,8 @@ import org.springframework.data.convert.EntityReader;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||
import org.springframework.data.mapping.Alias;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
/**
|
||||
* Marker interface for the converter, identifying the types to and from that can be converted.
|
||||
@@ -54,4 +56,9 @@ public interface CouchbaseConverter
|
||||
* @return the name of the field that will hold type information.
|
||||
*/
|
||||
String getTypeKey();
|
||||
|
||||
/**
|
||||
* @return the alias value for the type
|
||||
*/
|
||||
Alias getTypeAlias(TypeInformation<?> info);
|
||||
}
|
||||
|
||||
@@ -18,14 +18,19 @@ package org.springframework.data.couchbase.core.convert;
|
||||
|
||||
import org.springframework.data.convert.TypeMapper;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.mapping.Alias;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
/**
|
||||
* Marker interface for the TypeMapper.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public interface CouchbaseTypeMapper extends TypeMapper<CouchbaseDocument> {
|
||||
|
||||
String getTypeKey();
|
||||
|
||||
Alias getTypeAlias(TypeInformation<?> info);
|
||||
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.springframework.data.convert.DefaultTypeMapper;
|
||||
import org.springframework.data.convert.TypeAliasAccessor;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.mapping.Alias;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
/**
|
||||
* The Couchbase Type Mapper.
|
||||
@@ -72,4 +73,8 @@ public class DefaultCouchbaseTypeMapper extends DefaultTypeMapper<CouchbaseDocum
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getTypeAlias(TypeInformation<?> info) {
|
||||
return getAliasFor(info);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,17 +39,14 @@ import org.springframework.data.couchbase.core.mapping.CouchbaseList;
|
||||
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.event.AfterConvertCallback;
|
||||
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.couchbase.core.query.N1qlJoin;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.AssociationHandler;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.*;
|
||||
import org.springframework.data.mapping.PreferredConstructor.Parameter;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.data.mapping.callback.EntityCallbacks;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
@@ -182,6 +179,11 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
return typeMapper.getTypeKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getTypeAlias(TypeInformation<?> info) {
|
||||
return typeMapper.getTypeAlias(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> R read(final Class<R> clazz, final CouchbaseDocument source) {
|
||||
return read(ClassTypeInformation.from(clazz), source, null);
|
||||
|
||||
@@ -31,6 +31,9 @@ import org.springframework.data.couchbase.repository.query.StringBasedN1qlQueryP
|
||||
import org.springframework.data.couchbase.repository.support.MappingCouchbaseEntityInformation;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.Alias;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -47,8 +50,7 @@ public class Query {
|
||||
|
||||
static private final Pattern WHERE_PATTERN = Pattern.compile("\\sWHERE\\s");
|
||||
|
||||
public Query() {
|
||||
}
|
||||
public Query() {}
|
||||
|
||||
public Query(final QueryCriteria criteriaDefinition) {
|
||||
addCriteria(criteriaDefinition);
|
||||
@@ -60,8 +62,8 @@ public class Query {
|
||||
}
|
||||
|
||||
/**
|
||||
* set the postional parameters on the query object
|
||||
* There can only be named parameters or positional parameters - not both.
|
||||
* set the postional parameters on the query object There can only be named parameters or positional parameters - not
|
||||
* both.
|
||||
*
|
||||
* @param parameters - the positional parameters
|
||||
* @return - the query
|
||||
@@ -72,8 +74,8 @@ public class Query {
|
||||
}
|
||||
|
||||
/**
|
||||
* set the named parameters on the query object
|
||||
* There can only be named parameters or positional parameters - not both.
|
||||
* set the named parameters on the query object There can only be named parameters or positional parameters - not
|
||||
* both.
|
||||
*
|
||||
* @param parameters - the named parameters
|
||||
* @return - the query
|
||||
@@ -157,10 +159,8 @@ public class Query {
|
||||
sb.append(" ORDER BY ");
|
||||
sort.stream().forEach(order -> {
|
||||
if (order.isIgnoreCase()) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Given sort contained an Order for %s with ignore case! "
|
||||
+ "Couchbase N1QL does not support sorting ignoring case currently!",
|
||||
order.getProperty()));
|
||||
throw new IllegalArgumentException(String.format("Given sort contained an Order for %s with ignore case! "
|
||||
+ "Couchbase N1QL does not support sorting ignoring case currently!", order.getProperty()));
|
||||
}
|
||||
sb.append(order.getProperty()).append(" ").append(order.isAscending() ? "ASC," : "DESC,");
|
||||
});
|
||||
@@ -257,10 +257,15 @@ public class Query {
|
||||
StringBasedN1qlQueryParser.N1qlSpelValues getN1qlSpelValues(ReactiveCouchbaseTemplate template, Class domainClass,
|
||||
boolean isCount) {
|
||||
String typeKey = template.getConverter().getTypeKey();
|
||||
final CouchbasePersistentEntity<?> persistentEntity = template.getConverter().getMappingContext().getRequiredPersistentEntity(
|
||||
domainClass);
|
||||
final CouchbasePersistentEntity<?> persistentEntity = template.getConverter().getMappingContext()
|
||||
.getRequiredPersistentEntity(domainClass);
|
||||
MappingCouchbaseEntityInformation<?, Object> info = new MappingCouchbaseEntityInformation<>(persistentEntity);
|
||||
String typeValue = info.getJavaType().getName();
|
||||
TypeInformation<?> typeInfo = ClassTypeInformation.from(info.getJavaType());
|
||||
Alias alias = template.getConverter().getTypeAlias(typeInfo);
|
||||
if (alias != null && alias.isPresent()) {
|
||||
typeValue = alias.toString();
|
||||
}
|
||||
return StringBasedN1qlQueryParser.createN1qlSpelValues(template.getBucketName(), typeKey, typeValue, isCount);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,11 +28,13 @@ import java.util.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.TypeAlias;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseJsr310Converters.LocalDateTimeToLongConverter;
|
||||
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
|
||||
import org.springframework.data.couchbase.domain.Config;
|
||||
import org.springframework.data.couchbase.domain.User;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
|
||||
@@ -45,9 +47,11 @@ import org.springframework.data.mapping.MappingException;
|
||||
public class MappingCouchbaseConverterTests {
|
||||
|
||||
private static MappingCouchbaseConverter converter = new MappingCouchbaseConverter();
|
||||
private static MappingCouchbaseConverter customConverter = (new Config()).mappingCouchbaseConverter();
|
||||
|
||||
static {
|
||||
converter.afterPropertiesSet();
|
||||
customConverter.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -112,6 +116,18 @@ public class MappingCouchbaseConverterTests {
|
||||
assertThat(converted.getId()).isEqualTo(BaseEntity.ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writesNumberCustom() {
|
||||
CouchbaseDocument converted = new CouchbaseDocument();
|
||||
NumberEntity entity = new NumberEntity(42);
|
||||
|
||||
customConverter.write(entity, converted);
|
||||
Map<String, Object> result = converted.export();
|
||||
assertThat(result.get("t")).isEqualTo(entity.getClass().getName());
|
||||
assertThat(result.get("attr0")).isEqualTo(42L);
|
||||
assertThat(converted.getId()).isEqualTo(BaseEntity.ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readsNumber() {
|
||||
CouchbaseDocument source = new CouchbaseDocument();
|
||||
@@ -363,6 +379,40 @@ public class MappingCouchbaseConverterTests {
|
||||
assertThat(readConverted.listOfEmails.get(0).emailAddr).isEqualTo(listOfEmails.get(0).emailAddr);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writesAndReadsValueClassCustomType() {
|
||||
CouchbaseDocument converted = new CouchbaseDocument();
|
||||
|
||||
final String email = "foo@bar.com";
|
||||
final Emailx addy = new Emailx(email);
|
||||
List<Emailx> listOfEmails = new ArrayList<Emailx>();
|
||||
listOfEmails.add(addy);
|
||||
|
||||
ValueEntityx entity = new ValueEntityx(addy, listOfEmails);
|
||||
customConverter.write(entity, converted);
|
||||
Map<String, Object> result = converted.export();
|
||||
|
||||
assertThat(result.get("t")).isEqualTo(ValueEntityx.class.getAnnotation(TypeAlias.class).value());
|
||||
assertThat(result.get("email")).isEqualTo(new HashMap<String, Object>() {
|
||||
{
|
||||
put("emailAddr", 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);
|
||||
assertThat(readConverted.email.emailAddr).isEqualTo(addy.emailAddr);
|
||||
assertThat(readConverted.listOfEmails.get(0).emailAddr).isEqualTo(listOfEmails.get(0).emailAddr);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writesAndReadsCustomConvertedClass() {
|
||||
List<Object> converters = new ArrayList<>();
|
||||
@@ -693,6 +743,26 @@ public class MappingCouchbaseConverterTests {
|
||||
}
|
||||
}
|
||||
|
||||
@TypeAlias("x")
|
||||
static class ValueEntityx extends BaseEntity {
|
||||
private Emailx email;
|
||||
private List<Emailx> listOfEmails;
|
||||
|
||||
public ValueEntityx(Emailx email, List<Emailx> listOfEmails) {
|
||||
this.email = email;
|
||||
this.listOfEmails = listOfEmails;
|
||||
}
|
||||
}
|
||||
|
||||
// @TypeAlias("x")
|
||||
static class Emailx {
|
||||
private String emailAddr;
|
||||
|
||||
public Emailx(String emailAddr) {
|
||||
this.emailAddr = emailAddr;
|
||||
}
|
||||
}
|
||||
|
||||
static class CustomObject {
|
||||
private BigDecimal weight;
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
package org.springframework.data.couchbase.domain;
|
||||
|
||||
import org.springframework.data.couchbase.config.BeanNames;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions;
|
||||
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.auditing.DateTimeProvider;
|
||||
@@ -113,4 +117,38 @@ public class Config extends AbstractCouchbaseConfiguration {
|
||||
public DateTimeProvider testDateTimeProvider() {
|
||||
return new AuditingDateTimeProvider();
|
||||
}
|
||||
|
||||
// convenience constructor for tests
|
||||
public MappingCouchbaseConverter mappingCouchbaseConverter() {
|
||||
MappingCouchbaseConverter converter = null;
|
||||
try {
|
||||
// MappingCouchbaseConverter relies on a SimpleInformationMapper
|
||||
// that has an getAliasFor(info) that just returns getType().getName().
|
||||
// Our CustomMappingCouchbaseConverter uses a TypeBasedCouchbaseTypeMapper that will
|
||||
// use the DocumentType annotation
|
||||
converter = new CustomMappingCouchbaseConverter(couchbaseMappingContext(customConversions()), typeKey());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return converter;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Bean(name = "couchbaseMappingConverter")
|
||||
public MappingCouchbaseConverter mappingCouchbaseConverter(CouchbaseMappingContext couchbaseMappingContext,
|
||||
CouchbaseCustomConversions couchbaseCustomConversions) {
|
||||
// MappingCouchbaseConverter relies on a SimpleInformationMapper
|
||||
// that has an getAliasFor(info) that just returns getType().getName().
|
||||
// Our CustomMappingCouchbaseConverter uses a TypeBasedCouchbaseTypeMapper that will
|
||||
// use the DocumentType annotation
|
||||
MappingCouchbaseConverter converter = new CustomMappingCouchbaseConverter(couchbaseMappingContext, typeKey());
|
||||
converter.setCustomConversions(couchbaseCustomConversions);
|
||||
return converter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String typeKey() {
|
||||
return "t"; // this will override '_class', is passed in to new CustomMappingCouchbaseConverter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.springframework.data.couchbase.domain;
|
||||
|
||||
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
|
||||
public class CustomMappingCouchbaseConverter extends MappingCouchbaseConverter {
|
||||
|
||||
/**
|
||||
* this constructer creates a TypeBasedCouchbaseTypeMapper with the specified typeKey
|
||||
* while MappingCouchbaseConverter uses a DefaultCouchbaseTypeMapper
|
||||
* typeMapper = new DefaultCouchbaseTypeMapper(typeKey != null ? typeKey : TYPEKEY_DEFAULT);
|
||||
* @param mappingContext
|
||||
* @param typeKey - the typeKey to be used (normally "_class")
|
||||
*/
|
||||
public CustomMappingCouchbaseConverter(final MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> mappingContext, final String typeKey) {
|
||||
super(mappingContext, typeKey);
|
||||
this.typeMapper = new TypeBasedCouchbaseTypeMapper(typeKey);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,14 +18,11 @@ package org.springframework.data.couchbase.domain;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.annotation.CreatedBy;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedBy;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.annotation.*;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
|
||||
@Document
|
||||
@TypeAlias("p") // this will result in p : <className> or p : "t" using the CustomMappingCouchbaseConverter
|
||||
public class Person extends AbstractEntity {
|
||||
Optional<String> firstname;
|
||||
Optional<String> lastname;
|
||||
@@ -40,11 +37,13 @@ public class Person extends AbstractEntity {
|
||||
private long lastModification;
|
||||
|
||||
@CreatedDate
|
||||
private long creationDate; // =System.currentTimeMillis();
|
||||
private long creationDate;
|
||||
|
||||
@Version
|
||||
private long version;
|
||||
|
||||
private Object middlename;
|
||||
|
||||
public Person() {
|
||||
}
|
||||
|
||||
@@ -132,4 +131,7 @@ public class Person extends AbstractEntity {
|
||||
return "";
|
||||
}
|
||||
|
||||
public void setMiddlename(String middlename) {
|
||||
this.middlename = middlename;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.springframework.data.couchbase.domain;
|
||||
|
||||
import org.springframework.data.annotation.TypeAlias;
|
||||
import org.springframework.data.convert.SimpleTypeInformationMapper;
|
||||
import org.springframework.data.mapping.Alias;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
public class TypeAwareTypeInformationMapper extends SimpleTypeInformationMapper {
|
||||
|
||||
@Override
|
||||
public Alias createAliasFor(TypeInformation<?> type) {
|
||||
TypeAlias[] typeAlias = type.getType().getAnnotationsByType(TypeAlias.class);
|
||||
|
||||
if (typeAlias.length == 1) {
|
||||
return Alias.of(typeAlias[0].value());
|
||||
}
|
||||
|
||||
return super.createAliasFor(type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.springframework.data.couchbase.domain;
|
||||
|
||||
import org.springframework.data.convert.DefaultTypeMapper;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseTypeMapper;
|
||||
import org.springframework.data.couchbase.core.convert.DefaultCouchbaseTypeMapper;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.mapping.Alias;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
public class TypeBasedCouchbaseTypeMapper extends DefaultTypeMapper<CouchbaseDocument> implements CouchbaseTypeMapper {
|
||||
|
||||
private final String typeKey;
|
||||
|
||||
public TypeBasedCouchbaseTypeMapper(final String typeKey) {
|
||||
super(new DefaultCouchbaseTypeMapper.CouchbaseDocumentTypeAliasAccessor(typeKey),
|
||||
Collections.singletonList(new TypeAwareTypeInformationMapper()));
|
||||
this.typeKey = typeKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeKey() {
|
||||
return typeKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getTypeAlias(TypeInformation<?> info) {
|
||||
return getAliasFor(info);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user