Add suport for JsonValue annotation on Enums and JsonValue/JsonCreator otherwise. (#1618)
Closes #1617.
This commit is contained in:
@@ -18,11 +18,16 @@ package org.springframework.data.couchbase.config;
|
||||
|
||||
import static com.couchbase.client.java.ClusterOptions.clusterOptions;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.couchbase.client.java.encryption.annotation.Encrypted;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
@@ -37,9 +42,15 @@ import org.springframework.data.couchbase.CouchbaseClientFactory;
|
||||
import org.springframework.data.couchbase.SimpleCouchbaseClientFactory;
|
||||
import org.springframework.data.couchbase.core.CouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.convert.BooleanToEnumConverterFactory;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbasePropertyValueConverterFactory;
|
||||
import org.springframework.data.couchbase.core.convert.CryptoConverter;
|
||||
import org.springframework.data.couchbase.core.convert.IntegerToEnumConverterFactory;
|
||||
import org.springframework.data.couchbase.core.convert.JsonValueConverter;
|
||||
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.convert.OtherConverters;
|
||||
import org.springframework.data.couchbase.core.convert.StringToEnumConverterFactory;
|
||||
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;
|
||||
@@ -60,7 +71,6 @@ import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.couchbase.client.core.deps.com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.couchbase.client.core.encryption.CryptoManager;
|
||||
import com.couchbase.client.core.env.Authenticator;
|
||||
import com.couchbase.client.core.env.PasswordAuthenticator;
|
||||
@@ -72,6 +82,7 @@ import com.couchbase.client.java.env.ClusterEnvironment;
|
||||
import com.couchbase.client.java.json.JacksonTransformers;
|
||||
import com.couchbase.client.java.json.JsonValueModule;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
@@ -87,8 +98,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@Configuration
|
||||
public abstract class AbstractCouchbaseConfiguration {
|
||||
|
||||
ObjectMapper mapper;
|
||||
CryptoManager cryptoManager = null;
|
||||
volatile ObjectMapper objectMapper;
|
||||
volatile CryptoManager cryptoManager = null;
|
||||
|
||||
/**
|
||||
* The connection string which allows the SDK to connect to the cluster.
|
||||
@@ -157,7 +168,7 @@ public abstract class AbstractCouchbaseConfiguration {
|
||||
if (!nonShadowedJacksonPresent()) {
|
||||
throw new CouchbaseException("non-shadowed Jackson not present");
|
||||
}
|
||||
builder.jsonSerializer(JacksonJsonSerializer.create(getCouchbaseObjectMapper()));
|
||||
builder.jsonSerializer(JacksonJsonSerializer.create(getObjectMapper()));
|
||||
builder.cryptoManager(getCryptoManager());
|
||||
configureEnvironment(builder);
|
||||
return builder.build();
|
||||
@@ -277,10 +288,12 @@ public abstract class AbstractCouchbaseConfiguration {
|
||||
@Bean
|
||||
public TranslationService couchbaseTranslationService() {
|
||||
final JacksonTranslationService jacksonTranslationService = new JacksonTranslationService();
|
||||
jacksonTranslationService.setObjectMapper(getCouchbaseObjectMapper());
|
||||
jacksonTranslationService.setObjectMapper(getObjectMapper());
|
||||
jacksonTranslationService.afterPropertiesSet();
|
||||
// for sdk3, we need to ask the mapper _it_ uses to ignore extra fields...
|
||||
JacksonTransformers.MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
JacksonTransformers.MAPPER.configure(
|
||||
com.couchbase.client.core.deps.com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
|
||||
false);
|
||||
return jacksonTranslationService;
|
||||
}
|
||||
|
||||
@@ -298,21 +311,26 @@ public abstract class AbstractCouchbaseConfiguration {
|
||||
return mappingContext;
|
||||
}
|
||||
|
||||
private ObjectMapper getCouchbaseObjectMapper() {
|
||||
if (mapper != null) {
|
||||
return mapper;
|
||||
final public ObjectMapper getObjectMapper() {
|
||||
if(objectMapper == null) {
|
||||
synchronized (this) {
|
||||
if (objectMapper == null) {
|
||||
objectMapper = couchbaseObjectMapper();
|
||||
}
|
||||
}
|
||||
}
|
||||
return mapper = couchbaseObjectMapper();
|
||||
return objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ObjectMapper} for the jsonSerializer of the ClusterEnvironment
|
||||
* Creates a {@link ObjectMapper} for the jsonSerializer of the ClusterEnvironment and spring-data-couchbase
|
||||
* jacksonTranslationService and also some converters (EnumToObject, StringToEnum, IntegerToEnum)
|
||||
*
|
||||
* @return ObjectMapper
|
||||
*/
|
||||
public ObjectMapper couchbaseObjectMapper() {
|
||||
ObjectMapper om = new ObjectMapper(); // or use the one from the Java SDK (?) JacksonTransformers.MAPPER
|
||||
om.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
protected ObjectMapper couchbaseObjectMapper() {
|
||||
ObjectMapper om = new ObjectMapper();
|
||||
om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
om.registerModule(new JsonValueModule());
|
||||
if (getCryptoManager() != null) {
|
||||
om.registerModule(new EncryptionModule(getCryptoManager()));
|
||||
@@ -400,20 +418,35 @@ public abstract class AbstractCouchbaseConfiguration {
|
||||
List<GenericConverter> newConverters = new ArrayList();
|
||||
CustomConversions customConversions = CouchbaseCustomConversions.create(configurationAdapter -> {
|
||||
SimplePropertyValueConversions valueConversions = new SimplePropertyValueConversions();
|
||||
valueConversions.setConverterFactory(new CouchbasePropertyValueConverterFactory(cryptoManager));
|
||||
valueConversions.setConverterFactory(new CouchbasePropertyValueConverterFactory(cryptoManager, annotationToConverterMap()));
|
||||
valueConversions.setValueConverterRegistry(new PropertyValueConverterRegistrar().buildRegistry());
|
||||
valueConversions.afterPropertiesSet(); // wraps the CouchbasePropertyValueConverterFactory with CachingPVCFactory
|
||||
configurationAdapter.setPropertyValueConversions(valueConversions);
|
||||
configurationAdapter.registerConverters(newConverters);
|
||||
configurationAdapter.registerConverter(new OtherConverters.EnumToObject(getObjectMapper()));
|
||||
configurationAdapter.registerConverterFactory(new IntegerToEnumConverterFactory(getObjectMapper()));
|
||||
configurationAdapter.registerConverterFactory(new StringToEnumConverterFactory(getObjectMapper()));
|
||||
configurationAdapter.registerConverterFactory(new BooleanToEnumConverterFactory(getObjectMapper()));
|
||||
});
|
||||
return customConversions;
|
||||
}
|
||||
|
||||
Map<Class<? extends Annotation>,Class<?>> annotationToConverterMap(){
|
||||
Map<Class<? extends Annotation>,Class<?>> map= new HashMap();
|
||||
map.put(Encrypted.class, CryptoConverter.class);
|
||||
map.put(JsonValue.class, JsonValueConverter.class);
|
||||
return map;
|
||||
}
|
||||
/**
|
||||
* cryptoManager can be null, so it cannot be a bean and then used as an arg for bean methods
|
||||
*/
|
||||
private CryptoManager getCryptoManager() {
|
||||
if (cryptoManager == null) {
|
||||
cryptoManager = cryptoManager();
|
||||
if(cryptoManager == null) {
|
||||
synchronized (this) {
|
||||
if (cryptoManager == null) {
|
||||
cryptoManager = cryptoManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
return cryptoManager;
|
||||
}
|
||||
|
||||
@@ -117,10 +117,8 @@ public abstract class AbstractCouchbaseConverter implements CouchbaseConverter,
|
||||
return null;
|
||||
}
|
||||
if (processValueConverter && conversions.hasValueConverter(prop)) {
|
||||
CouchbaseDocument encrypted = (CouchbaseDocument) conversions.getPropertyValueConversions()
|
||||
.getValueConverter(prop)
|
||||
.write(value, new CouchbaseConversionContext(prop, (MappingCouchbaseConverter) this, accessor));
|
||||
return encrypted;
|
||||
return conversions.getPropertyValueConversions().getValueConverter(prop).write(value,
|
||||
new CouchbaseConversionContext(prop, (MappingCouchbaseConverter) this, accessor));
|
||||
}
|
||||
Class<?> targetClass = this.conversions.getCustomWriteTarget(value.getClass()).orElse(null);
|
||||
|
||||
@@ -134,7 +132,9 @@ public abstract class AbstractCouchbaseConverter implements CouchbaseConverter,
|
||||
Object result = this.conversions.getCustomWriteTarget(prop.getType()) //
|
||||
.map(it -> this.conversionService.convert(value, new TypeDescriptor(prop.getField()),
|
||||
TypeDescriptor.valueOf(it))) //
|
||||
.orElseGet(() -> Enum.class.isAssignableFrom(value.getClass()) ? ((Enum<?>) value).name() : value);
|
||||
.orElse(value);
|
||||
// superseded by Enum converters
|
||||
// .orElseGet(() -> Enum.class.isAssignableFrom(value.getClass()) ? ((Enum<?>) value).name() : value);
|
||||
|
||||
return result;
|
||||
|
||||
@@ -160,7 +160,7 @@ public abstract class AbstractCouchbaseConverter implements CouchbaseConverter,
|
||||
Class<?> elementType = value.getClass();
|
||||
|
||||
if (elementType == null || conversions.isSimpleType(elementType)) {
|
||||
value = Enum.class.isAssignableFrom(value.getClass()) ? ((Enum<?>) value).name() : value;
|
||||
// superseded by EnumCvtrs value = Enum.class.isAssignableFrom(value.getClass()) ? ((Enum<?>) value).name() : value;
|
||||
} else if (value instanceof Collection || elementType.isArray()) {
|
||||
TypeInformation<?> type = ClassTypeInformation.from(value.getClass());
|
||||
value = ((MappingCouchbaseConverter) this).writeCollectionInternal(MappingCouchbaseConverter.asCollection(value),
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package org.springframework.data.couchbase.core.convert;
|
||||
/*
|
||||
* Copyright 2022 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.ConverterFactory;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.core.encryption.CryptoManager;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Reading Converter factory for Enums. This differs from the one provided in org.springframework.core.convert.support
|
||||
* by getting the result from the jackson objectmapper (which will process @JsonValue annotations) This is registered in
|
||||
* {@link org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration#customConversions(CryptoManager)}.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@ReadingConverter
|
||||
public class BooleanToEnumConverterFactory implements ConverterFactory<Boolean, Enum> {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public BooleanToEnumConverterFactory(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Enum> Converter<Boolean, T> getConverter(Class<T> targetType) {
|
||||
return new BooleanToEnum(getEnumType(targetType), objectMapper);
|
||||
}
|
||||
|
||||
public static Class<?> getEnumType(Class<?> targetType) {
|
||||
Class<?> enumType = targetType;
|
||||
while (enumType != null && !enumType.isEnum()) {
|
||||
enumType = enumType.getSuperclass();
|
||||
}
|
||||
Assert.notNull(enumType, () -> "The target type " + targetType.getName() + " does not refer to an enum");
|
||||
return enumType;
|
||||
}
|
||||
|
||||
private static class BooleanToEnum<T extends Enum> implements Converter<Boolean, T> {
|
||||
|
||||
private final Class<T> enumType;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
BooleanToEnum(Class<T> enumType, ObjectMapper objectMapper) {
|
||||
this.enumType = enumType;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public T convert(Boolean source) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue("\"" + source + "\"", enumType);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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
|
||||
*
|
||||
* https://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;
|
||||
|
||||
/**
|
||||
* PropertyValueConverter throws this when cannot convert the property. The caller should catch this and resort to other
|
||||
* means for creating the value.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class ConverterHasNoConversion extends RuntimeException {}
|
||||
@@ -44,8 +44,6 @@ import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.java.encryption.annotation.Encrypted;
|
||||
|
||||
/**
|
||||
* Value object to capture custom conversion.
|
||||
* <p>
|
||||
@@ -112,9 +110,6 @@ public class CouchbaseCustomConversions extends org.springframework.data.convert
|
||||
|
||||
@Override
|
||||
public boolean hasValueConverter(PersistentProperty<?> property) {
|
||||
if (property.findAnnotation(Encrypted.class) != null) {
|
||||
return true;
|
||||
}
|
||||
return super.hasValueConverter(property);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2022 the original author or authors
|
||||
* Copyright 2022 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.
|
||||
@@ -13,73 +13,156 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.core.convert;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.HashMap;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.data.convert.PropertyValueConverter;
|
||||
import org.springframework.data.convert.PropertyValueConverterFactory;
|
||||
import org.springframework.data.convert.ValueConversionContext;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
|
||||
import com.couchbase.client.core.encryption.CryptoManager;
|
||||
import com.couchbase.client.java.encryption.annotation.Encrypted;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Accept the Couchbase @Encrypted annotation in addition to @ValueConverter
|
||||
* Accept the Couchbase @Encrypted and @JsonValue annotations in addition to @ValueConverter annotation.<br>
|
||||
* There can only be one propertyValueConverter for a property. Although there maybe be multiple annotations,
|
||||
* getConverter(property) only returns one converter (a ChainedPropertyValueConverter might be useful). Note that
|
||||
* valueConversions.afterPropertiesSet() (see
|
||||
* {@link org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration#customConversions(CryptoManager)}
|
||||
* encapsulates this in a CachingPropertyValueConverterFactory which caches by 'property'. Although
|
||||
* CachingPropertyValueConverterFactory does have the functionality to cache by a type, it only caches by the type
|
||||
* specified on an @ValueConverter annotation.To avoid having identical converter instances for each instance of a class
|
||||
* containing an @JsonValue annotation, converterCacheForType is used.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class CouchbasePropertyValueConverterFactory implements PropertyValueConverterFactory {
|
||||
|
||||
CryptoManager cryptoManager;
|
||||
Map<Class<? extends PropertyValueConverter<?, ?, ?>>, PropertyValueConverter<?, ?, ?>> converterCache = new HashMap<>();
|
||||
final CryptoManager cryptoManager;
|
||||
final Map<Class<? extends Annotation>, Class<?>> annotationToConverterMap;
|
||||
static protected final Map<Class<?>, Optional<PropertyValueConverter<?, ?, ?>>> converterCacheForType = new ConcurrentHashMap<>();
|
||||
|
||||
public CouchbasePropertyValueConverterFactory(CryptoManager cryptoManager) {
|
||||
public CouchbasePropertyValueConverterFactory(CryptoManager cryptoManager,
|
||||
Map<Class<? extends Annotation>, Class<?>> annotationToConverterMap) {
|
||||
this.cryptoManager = cryptoManager;
|
||||
this.annotationToConverterMap = annotationToConverterMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param property must not be {@literal null}.
|
||||
* @return
|
||||
* @param <DV> destination value
|
||||
* @param <SV> source value
|
||||
* @param <P> context
|
||||
*/
|
||||
@Override
|
||||
public <DV, SV, P extends ValueConversionContext<?>> PropertyValueConverter<DV, SV, P> getConverter(
|
||||
PersistentProperty<?> property) {
|
||||
|
||||
PropertyValueConverter<DV, SV, P> valueConverter = PropertyValueConverterFactory.super.getConverter(property);
|
||||
if (valueConverter != null) {
|
||||
return valueConverter;
|
||||
}
|
||||
Encrypted encryptedAnn = property.findAnnotation(Encrypted.class);
|
||||
if (encryptedAnn != null) {
|
||||
Class cryptoConverterClass = CryptoConverter.class;
|
||||
return getConverter((Class<PropertyValueConverter<DV, SV, P>>) cryptoConverterClass);
|
||||
} else {
|
||||
|
||||
// this will return the converter for the first annotation that requires a PropertyValueConverter like @Encrypted
|
||||
for (Annotation ann : property.getField().getAnnotations()) {
|
||||
Class<?> converterClass = converterFromFieldAnnotation(ann);
|
||||
if (converterClass != null) {
|
||||
return getConverter((Class<PropertyValueConverter<DV, SV, P>>) converterClass, property);
|
||||
}
|
||||
}
|
||||
|
||||
if (property.getType().isEnum()) { // Enums have type-based converters for JsonValue/Creator. see OtherConverters.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Maybe the type of the property has annotations that indicate a converter (like a method with a @JsonValue)
|
||||
return (PropertyValueConverter<DV, SV, P>) converterCacheForType
|
||||
.computeIfAbsent(property.getType(), p -> Optional.ofNullable((maybeTypePropertyConverter(property))))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* lookup the converter class from the annotation. Analogous to getting the converter class from the value() attribute
|
||||
* of the @ValueProperty annotation
|
||||
*
|
||||
* @param ann the annotation
|
||||
* @return the class of the converter
|
||||
*/
|
||||
private Class<?> converterFromFieldAnnotation(Annotation ann) {
|
||||
return annotationToConverterMap.get(ann.annotationType());
|
||||
}
|
||||
|
||||
<DV, SV, P extends ValueConversionContext<?>> PropertyValueConverter<DV, SV, P> maybeTypePropertyConverter(
|
||||
PersistentProperty<?> property) {
|
||||
|
||||
Class<?> type = property.getType();
|
||||
|
||||
// find the annotated method to determine if a converter is required, and cache it.
|
||||
Method jsonValueMethod = null;
|
||||
for (Method m : type.getDeclaredMethods()) {
|
||||
JsonValue jsonValueAnn = m.getAnnotation(JsonValue.class);
|
||||
if (jsonValueAnn != null && jsonValueAnn.value()) {
|
||||
Class jsonValueConverterClass = converterFromFieldAnnotation(jsonValueAnn);
|
||||
if (jsonValueConverterClass != null) {
|
||||
jsonValueMethod = m;
|
||||
jsonValueMethod.setAccessible(true);
|
||||
JsonValueConverter.valueMethodCache.put(type, jsonValueMethod);
|
||||
Constructor<?> jsonCreatorMethod = null;
|
||||
for (Constructor<?> c : type.getConstructors()) {
|
||||
JsonCreator jsonCreatorAnn = c.getAnnotation(JsonCreator.class);
|
||||
if (jsonCreatorAnn != null && !jsonCreatorAnn.mode().equals(JsonCreator.Mode.DISABLED)) {
|
||||
jsonCreatorMethod = c;
|
||||
jsonCreatorMethod.setAccessible(true);
|
||||
JsonValueConverter.creatorMethodCache.put(type, jsonCreatorMethod);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return getConverter((Class<PropertyValueConverter<DV, SV, P>>) jsonValueConverterClass, property);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null; // we didn't find a property value converter to use
|
||||
}
|
||||
|
||||
@Override
|
||||
public <DV, SV, P extends ValueConversionContext<?>> PropertyValueConverter<DV, SV, P> getConverter(
|
||||
Class<? extends PropertyValueConverter<DV, SV, P>> converterType) {
|
||||
return getConverter(converterType, null);
|
||||
}
|
||||
|
||||
PropertyValueConverter<?, ?, ?> converter = converterCache.get(converterType);
|
||||
if (converter != null) {
|
||||
return (PropertyValueConverter<DV, SV, P>) converter;
|
||||
}
|
||||
/**
|
||||
* @param converterType
|
||||
* @param property
|
||||
* @return
|
||||
* @param <DV>
|
||||
* @param <SV>
|
||||
* @param <P>
|
||||
*/
|
||||
public <DV, SV, P extends ValueConversionContext<?>> PropertyValueConverter<DV, SV, P> getConverter(
|
||||
Class<? extends PropertyValueConverter<DV, SV, P>> converterType, PersistentProperty<?> property) {
|
||||
|
||||
// CryptoConverter takes a cryptoManager argument
|
||||
if (CryptoConverter.class.isAssignableFrom(converterType)) {
|
||||
converter = new CryptoConverter(cryptoManager);
|
||||
} else {
|
||||
return (PropertyValueConverter<DV, SV, P>) new CryptoConverter(cryptoManager);
|
||||
} else if (property != null) { // try constructor that takes PersistentProperty
|
||||
try {
|
||||
Constructor constructor = converterType.getConstructor();
|
||||
converter = (PropertyValueConverter<?, ?, ?>) constructor.newInstance();
|
||||
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
Constructor<?> constructor = converterType.getConstructor(PersistentProperty.class);
|
||||
return (PropertyValueConverter<DV, SV, P>) BeanUtils.instantiateClass(constructor, property);
|
||||
} catch (NoSuchMethodException e) {}
|
||||
}
|
||||
converterCache.put((Class<? extends PropertyValueConverter<DV, SV, P>>) converter.getClass(), converter);
|
||||
return (PropertyValueConverter<DV, SV, P>) converter;
|
||||
|
||||
// there is no constructor that takes a property, fall-back to no-args constructor
|
||||
return BeanUtils.instantiateClass(converterType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,15 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.core.convert.ConversionFailedException;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.ConverterNotFoundException;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.convert.PropertyValueConverter;
|
||||
import org.springframework.data.convert.ValueConversionContext;
|
||||
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.CouchbasePersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
@@ -40,7 +43,8 @@ import com.couchbase.client.java.json.JsonObject;
|
||||
import com.couchbase.client.java.json.JsonValue;
|
||||
|
||||
/**
|
||||
* Encrypt/Decrypted properties annotated with
|
||||
* Encrypt/Decrypted properties annotated. This is registered in
|
||||
* {@link org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration#customConversions(CryptoManager)}.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@@ -68,7 +72,8 @@ public class CryptoConverter implements
|
||||
CouchbaseConversionContext ctx = (CouchbaseConversionContext) context;
|
||||
CouchbasePersistentProperty property = ctx.getProperty();
|
||||
byte[] plainText = coerceToBytesWrite(property, ctx.getAccessor(), ctx);
|
||||
Map<String, Object> encrypted = cryptoManager().encrypt(plainText, ctx.getProperty().findAnnotation(Encrypted.class).encrypter());
|
||||
Map<String, Object> encrypted = cryptoManager().encrypt(plainText,
|
||||
ctx.getProperty().findAnnotation(Encrypted.class).encrypter());
|
||||
return new CouchbaseDocument().setContent(encrypted);
|
||||
}
|
||||
|
||||
@@ -83,6 +88,9 @@ public class CryptoConverter implements
|
||||
if ("null".equals(decryptedString)) {
|
||||
return null;
|
||||
}
|
||||
// TODO - as-is, this never gets ran through ObjectMapper() -
|
||||
// TODO - i.e. @JsonValue etc will not be processed by ObjectMapper
|
||||
|
||||
/* this what we would do if we could use a JsonParser with a beanPropertyTypeRef
|
||||
final JsonParser plaintextParser = p.getCodec().getFactory().createParser(plaintext);
|
||||
plaintextParser.setCodec(p.getCodec());
|
||||
@@ -125,7 +133,7 @@ public class CryptoConverter implements
|
||||
}
|
||||
plainText = ja.toBytes();
|
||||
} else if (cnvs.isSimpleType(sourceType)) { // simpleType
|
||||
String plainString = value != null ? value.toString() : null;
|
||||
String plainString = value != null ? value.toString() : null; // TODO - this will ignore @JsonValue
|
||||
if ((sourceType == String.class || targetType == String.class) || sourceType == Character.class
|
||||
|| sourceType == char.class || Enum.class.isAssignableFrom(sourceType)
|
||||
|| Locale.class.isAssignableFrom(sourceType)) {
|
||||
@@ -265,7 +273,7 @@ public class CryptoConverter implements
|
||||
} else if (Character.class.isAssignableFrom(o.getClass())) {
|
||||
o = ((Character) o).toString();
|
||||
} else if (Enum.class.isAssignableFrom(o.getClass())) {
|
||||
o = ((Enum) o).name();
|
||||
o = ((Enum) o).name(); // TODO - this is will ignore @JsonValue
|
||||
} else { // punt
|
||||
o = o.toString();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2022 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
|
||||
*
|
||||
* https://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;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.ConverterFactory;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.core.encryption.CryptoManager;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Reading Converter factory for Enums. This differs from the one provided in org.springframework.core.convert.support
|
||||
* by getting the result from the jackson objectmapper (which will process @JsonValue annotations) This is registered in
|
||||
* {@link org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration#customConversions(CryptoManager)}.
|
||||
* This will take precedence over {@link org.springframework.core.convert.support.IntegerToEnumConverterFactory}
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@ReadingConverter
|
||||
public class IntegerToEnumConverterFactory implements ConverterFactory<Integer, Enum> {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public IntegerToEnumConverterFactory(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Enum> Converter<Integer, T> getConverter(Class<T> targetType) {
|
||||
return new ObjectToEnum(getEnumType(targetType), objectMapper);
|
||||
}
|
||||
|
||||
public static Class<?> getEnumType(Class<?> targetType) {
|
||||
Class<?> enumType = targetType;
|
||||
while (enumType != null && !enumType.isEnum()) {
|
||||
enumType = enumType.getSuperclass();
|
||||
}
|
||||
Assert.notNull(enumType, () -> "The target type " + targetType.getName() + " does not refer to an enum");
|
||||
return enumType;
|
||||
}
|
||||
|
||||
private static class ObjectToEnum<T extends Enum> implements Converter<Integer, T> {
|
||||
|
||||
private final Class<T> enumType;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
ObjectToEnum(Class<T> enumType, ObjectMapper objectMapper) {
|
||||
this.enumType = enumType;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public T convert(Integer source) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(source.toString(), enumType);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2022 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
|
||||
*
|
||||
* https://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;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.data.convert.PropertyValueConverter;
|
||||
import org.springframework.data.convert.ValueConversionContext;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
|
||||
/**
|
||||
* Converter for non-Enum types that have @JsonValue and possibly an @JsonCreator annotated methods.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class JsonValueConverter
|
||||
implements PropertyValueConverter<Object, Object, ValueConversionContext<? extends PersistentProperty<?>>> {
|
||||
|
||||
static protected final Map<Class<?>, Method> valueMethodCache = new ConcurrentHashMap<>();
|
||||
static protected final Map<Class<?>, Constructor<?>> creatorMethodCache = new ConcurrentHashMap<>();
|
||||
static private final ConverterHasNoConversion CONVERTER_HAS_NO_CONVERSION = new ConverterHasNoConversion();
|
||||
|
||||
@Override
|
||||
public Object read(Object value, ValueConversionContext<? extends PersistentProperty<?>> context) {
|
||||
Class<?> type = context.getProperty().getType();
|
||||
|
||||
// if there was a @JsonCreator method, use it
|
||||
if (getJsonCreatorMethod(type) != null) {
|
||||
try {
|
||||
return getJsonCreatorMethod(type).newInstance(value);
|
||||
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// fall-through in MappingCouchbaseConverter.readValue(), maybe there is an @PersistenceCreator that takes the arg.
|
||||
throw CONVERTER_HAS_NO_CONVERSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object write(Object value, ValueConversionContext<? extends PersistentProperty<?>> context) {
|
||||
Class<?> type = value.getClass();
|
||||
try {
|
||||
return getJsonValueMethod(type).invoke(value);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Method getJsonValueMethod(Class<?> type) {
|
||||
return valueMethodCache.get(type);
|
||||
}
|
||||
|
||||
private Constructor<?> getJsonCreatorMethod(Class<?> type) {
|
||||
return creatorMethodCache.get(type);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -248,8 +248,6 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
|
||||
CouchbasePersistentEntity<R> entity = (CouchbasePersistentEntity<R>) mappingContext
|
||||
.getRequiredPersistentEntity(typeToUse);
|
||||
if (source.containsKey("encbooleans"))
|
||||
System.err.println(source);
|
||||
return read(entity, source, parent);
|
||||
}
|
||||
|
||||
@@ -402,6 +400,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
}
|
||||
|
||||
if (Enum.class.isAssignableFrom(target)) {
|
||||
// no longer needed with Enum converters
|
||||
return Enum.valueOf((Class<Enum>) target, value.toString());
|
||||
}
|
||||
|
||||
@@ -772,7 +771,8 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
type, prop, accessor));
|
||||
} else {
|
||||
CouchbaseDocument embeddedDoc = new CouchbaseDocument();
|
||||
writeInternalRoot(element, embeddedDoc, prop != null ? prop.getTypeInformation() : TypeInformation.of(elementType), false, prop);
|
||||
writeInternalRoot(element, embeddedDoc,
|
||||
prop != null ? prop.getTypeInformation() : TypeInformation.of(elementType), false, prop);
|
||||
target.put(embeddedDoc);
|
||||
}
|
||||
|
||||
@@ -854,7 +854,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
|
||||
/**
|
||||
* This does process PropertyValueConversions
|
||||
*
|
||||
*
|
||||
* @param value
|
||||
* @param accessor
|
||||
* @return
|
||||
@@ -954,18 +954,25 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
public <R> R readValue(Object value, CouchbasePersistentProperty prop, Object parent, boolean noDecrypt) {
|
||||
Class<?> rawType = prop.getType();
|
||||
if (conversions.hasValueConverter(prop) && !noDecrypt) {
|
||||
return (R) conversions.getPropertyValueConversions().getValueConverter(prop).read(value,
|
||||
new CouchbaseConversionContext(prop, this, null));
|
||||
} else if (conversions.hasCustomReadTarget(value.getClass(), rawType)) {
|
||||
try {
|
||||
return (R) conversions.getPropertyValueConversions().getValueConverter(prop).read(value,
|
||||
new CouchbaseConversionContext(prop, this, null));
|
||||
} catch (ConverterHasNoConversion noConversion) {
|
||||
; // ignore
|
||||
}
|
||||
}
|
||||
if (conversions.hasCustomReadTarget(value.getClass(), rawType)) {
|
||||
TypeInformation ti = ClassTypeInformation.from(value.getClass());
|
||||
return (R) conversionService.convert(value, ti.toTypeDescriptor(), new TypeDescriptor(prop.getField()));
|
||||
} else if (value instanceof CouchbaseDocument) {
|
||||
return (R) read(prop.getTypeInformation(), (CouchbaseDocument) value, parent);
|
||||
} else if (value instanceof CouchbaseList) {
|
||||
return (R) readCollection(prop.getTypeInformation(), (CouchbaseList) value, parent);
|
||||
} else {
|
||||
return (R) getPotentiallyConvertedSimpleRead(value, prop);// passes PersistentProperty with annotations
|
||||
}
|
||||
if (value instanceof CouchbaseDocument) {
|
||||
return (R) read(prop.getTypeInformation(), (CouchbaseDocument) value, parent);
|
||||
}
|
||||
if (value instanceof CouchbaseList) {
|
||||
return (R) readCollection(prop.getTypeInformation(), (CouchbaseList) value, parent);
|
||||
}
|
||||
return (R) getPotentiallyConvertedSimpleRead(value, prop);// passes PersistentProperty with annotations
|
||||
|
||||
}
|
||||
|
||||
private ConvertingPropertyAccessor<Object> getPropertyAccessor(Object source) {
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package org.springframework.data.couchbase.core.convert;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -24,13 +27,19 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectWriter;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.util.Base64Utils;
|
||||
|
||||
import com.couchbase.client.core.encryption.CryptoManager;
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Out of the box conversions for java dates and calendars.
|
||||
* Out of the box conversions for Other types.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@@ -58,7 +67,10 @@ public final class OtherConverters {
|
||||
converters.add(StringToCharArray.INSTANCE);
|
||||
converters.add(ClassToString.INSTANCE);
|
||||
converters.add(StringToClass.INSTANCE);
|
||||
|
||||
// EnumToObject, IntegerToEnumConverterFactory and StringToEnumConverterFactory are
|
||||
// registered in
|
||||
// {@link org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration#customConversions(
|
||||
// CryptoManager)} as they require an ObjectMapper
|
||||
return converters;
|
||||
}
|
||||
|
||||
@@ -148,7 +160,7 @@ public final class OtherConverters {
|
||||
|
||||
@Override
|
||||
public String convert(char[] source) {
|
||||
return source == null ? null : new String(source) ;
|
||||
return source == null ? null : new String(source);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,14 +174,13 @@ public final class OtherConverters {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@WritingConverter
|
||||
public enum ClassToString implements Converter<Class<?>, String> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String convert(Class<?> source) {
|
||||
return source == null ? null : source.getClass().getName() ;
|
||||
return source == null ? null : source.getClass().getName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,4 +198,38 @@ public final class OtherConverters {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writing converter for Enums. This is registered in
|
||||
* {@link org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration#customConversions( CryptoManager)}.
|
||||
* The corresponding reading converters are in {@link IntegerToEnumConverterFactory} and
|
||||
* {@link StringToEnumConverterFactory}
|
||||
*/
|
||||
|
||||
@WritingConverter
|
||||
public static class EnumToObject implements Converter<Enum<?>, Object> {
|
||||
private final ObjectMapper objectMapper;
|
||||
private static final JsonFactory factory = new JsonFactory();
|
||||
|
||||
public EnumToObject(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convert(Enum<?> source) {
|
||||
try (Writer writer = new StringWriter(); JsonGenerator generator = factory.createGenerator(writer)) {
|
||||
objectMapper.writeValue(generator, source);
|
||||
String s = writer.toString();
|
||||
if (s != null && s.startsWith("\"")) {
|
||||
return objectMapper.readValue(s,String.class);
|
||||
}
|
||||
if ("true".equals(s) || "false".equals(s)) {
|
||||
return objectMapper.readValue(s,Boolean.class);
|
||||
}
|
||||
return objectMapper.readValue(s,Number.class);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2022 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
|
||||
*
|
||||
* https://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;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.ConverterFactory;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.couchbase.client.core.encryption.CryptoManager;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Reading Converter factory for Enums. This differs from the one provided in org.springframework.core.convert.support
|
||||
* by getting the result from the jackson objectmapper (which will process @JsonValue annotations) This is registered in
|
||||
* {@link org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration#customConversions(CryptoManager)}.
|
||||
* This will take precedence over {@link org.springframework.core.convert.support.StringToEnumConverterFactory}
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@ReadingConverter
|
||||
public class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public StringToEnumConverterFactory(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
|
||||
return new StringToEnum(getEnumType(targetType), objectMapper);
|
||||
}
|
||||
|
||||
public static Class<?> getEnumType(Class<?> targetType) {
|
||||
Class<?> enumType = targetType;
|
||||
while (enumType != null && !enumType.isEnum()) {
|
||||
enumType = enumType.getSuperclass();
|
||||
}
|
||||
Assert.notNull(enumType, () -> "The target type " + targetType.getName() + " does not refer to an enum");
|
||||
return enumType;
|
||||
}
|
||||
|
||||
private static class StringToEnum<T extends Enum> implements Converter<String, T> {
|
||||
|
||||
private final Class<T> enumType;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
StringToEnum(Class<T> enumType, ObjectMapper objectMapper) {
|
||||
this.enumType = enumType;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public T convert(String source) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
return objectMapper.convertValue(source, enumType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,6 +90,7 @@ class CouchbaseCacheIntegrationTests extends JavaIntegrationTests {
|
||||
t0 = System.currentTimeMillis();
|
||||
users = userRepository.getByFirstname(user.getFirstname());
|
||||
assert (System.currentTimeMillis() - t0 < 100);
|
||||
userRepository.delete(user);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -26,6 +26,8 @@ import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
import org.springframework.data.couchbase.core.mapping.Expiration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
/**
|
||||
* Airport entity
|
||||
*
|
||||
@@ -39,7 +41,7 @@ public class Airport extends ComparableEntity {
|
||||
|
||||
String iata;
|
||||
|
||||
String icao;
|
||||
@JsonFormat(pattern = "abc") String icao;
|
||||
|
||||
@Version Number version;
|
||||
|
||||
@@ -48,6 +50,15 @@ public class Airport extends ComparableEntity {
|
||||
@Max(2) long size;
|
||||
private long someNumber;
|
||||
|
||||
public AirportJsonValuedObject jvObject;
|
||||
|
||||
{
|
||||
this.jvObject = new AirportJsonValuedObject();
|
||||
this.jvObject.theValue = "first";
|
||||
}
|
||||
|
||||
public Airport() {}
|
||||
|
||||
@PersistenceConstructor
|
||||
public Airport(String key, String iata, String icao) {
|
||||
this.key = key;
|
||||
@@ -99,4 +110,5 @@ public class Airport extends ComparableEntity {
|
||||
public void setSize(long size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2022 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
|
||||
*
|
||||
* https://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.domain;
|
||||
|
||||
import jakarta.validation.constraints.Max;
|
||||
|
||||
import org.springframework.data.annotation.CreatedBy;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.PersistenceConstructor;
|
||||
import org.springframework.data.annotation.TypeAlias;
|
||||
import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
import org.springframework.data.couchbase.core.mapping.Expiration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
/**
|
||||
* AirportJsonValue entity. From Airport.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@Document
|
||||
@TypeAlias("airport")
|
||||
public class AirportJsonValue extends ComparableEntity {
|
||||
@Id String key;
|
||||
|
||||
String iata;
|
||||
|
||||
@JsonFormat(pattern = "abc") String icao;
|
||||
|
||||
ETurbulenceCategory turbulence1;
|
||||
ETurbulenceCategory turbulence2;
|
||||
EITurbulenceCategory turbulence3;
|
||||
EJsonCreatorTurbulenceCategory turbulence4;
|
||||
EBTurbulenceCategory turbulence5 ;
|
||||
|
||||
@Version Number version;
|
||||
@CreatedBy private String createdBy;
|
||||
@Expiration private long expiration;
|
||||
@Max(2) long size;
|
||||
|
||||
public AirportJsonValuedObject jvObject;
|
||||
|
||||
{
|
||||
this.jvObject = new AirportJsonValuedObject();
|
||||
}
|
||||
|
||||
public AirportJsonValue() {}
|
||||
|
||||
@PersistenceConstructor
|
||||
public AirportJsonValue(String key, String iata, String icao) {
|
||||
this.key = key;
|
||||
this.iata = iata;
|
||||
this.icao = icao;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public String getIata() {
|
||||
return iata;
|
||||
}
|
||||
|
||||
public String getIcao() {
|
||||
return icao;
|
||||
}
|
||||
|
||||
public long getExpiration() {
|
||||
return expiration;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(long size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public void setTurbulence1(ETurbulenceCategory turbulence1) {
|
||||
this.turbulence1 = turbulence1;
|
||||
}
|
||||
|
||||
public ETurbulenceCategory getTurbulence1() {
|
||||
return turbulence1;
|
||||
}
|
||||
|
||||
public void setTurbulence2(ETurbulenceCategory turbulence) {
|
||||
this.turbulence2 = turbulence;
|
||||
}
|
||||
|
||||
public ETurbulenceCategory getTurbulence2() {
|
||||
return turbulence2;
|
||||
}
|
||||
|
||||
public void setTurbulence3(EITurbulenceCategory turbulence3) {
|
||||
this.turbulence3 = turbulence3;
|
||||
}
|
||||
|
||||
public EITurbulenceCategory getTurbulence3() {
|
||||
return turbulence3;
|
||||
}
|
||||
|
||||
public void setTurbulence4(EJsonCreatorTurbulenceCategory turbulence4) {
|
||||
this.turbulence4 = turbulence4;
|
||||
}
|
||||
|
||||
public EJsonCreatorTurbulenceCategory getTurbulence4() {
|
||||
return turbulence4;
|
||||
}
|
||||
|
||||
public void setTurbulence5(EBTurbulenceCategory turbulence5) {
|
||||
this.turbulence5 = turbulence5;
|
||||
}
|
||||
|
||||
public EBTurbulenceCategory getTurbulence5() {
|
||||
return turbulence5;
|
||||
}
|
||||
|
||||
public void setJvObject(AirportJsonValuedObject jvObject) {
|
||||
this.jvObject = jvObject;
|
||||
}
|
||||
|
||||
public AirportJsonValuedObject getJvObject() {
|
||||
return jvObject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2022 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
|
||||
*
|
||||
* https://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.domain;
|
||||
|
||||
import org.springframework.data.couchbase.repository.CouchbaseRepository;
|
||||
import org.springframework.data.couchbase.repository.DynamicProxyable;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
* AirportJsonValue repository for testing <br>
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@Repository
|
||||
public interface AirportJsonValueRepository
|
||||
extends CouchbaseRepository<AirportJsonValue, String>, DynamicProxyable<AirportJsonValueRepository> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2022 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
|
||||
*
|
||||
* https://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.domain;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.annotation.PersistenceCreator;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
/**
|
||||
* A non-Enum object that has @JsonValue annotated method.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class AirportJsonValuedObject {
|
||||
|
||||
public AirportJsonValuedObject() {}
|
||||
|
||||
@PersistenceCreator
|
||||
@JsonCreator
|
||||
public AirportJsonValuedObject(String theValue) {
|
||||
this.theValue = reverseMap.get(theValue);
|
||||
if (this.theValue == null) {
|
||||
throw new RuntimeException(
|
||||
"invalid value for " + this.getClass().getName() + " : " + theValue + ". Must be one of " + reverseMap);
|
||||
}
|
||||
}
|
||||
|
||||
public String theValue;
|
||||
static Map<String, String> map = Map.of("first", "1st", "second", "2nd");
|
||||
static Map<String, String> reverseMap = new HashMap<>();
|
||||
static {
|
||||
map.entrySet().stream().map((entry) -> reverseMap.put(entry.getValue(), entry.getKey()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public String getMapped() {
|
||||
return map.get(theValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.springframework.data.couchbase.domain;
|
||||
|
||||
/*
|
||||
* Copyright 2022 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
/**
|
||||
* An enum that has an Boolean getCode() method.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public enum EBTurbulenceCategory {
|
||||
T(true), F(false);
|
||||
|
||||
private final Boolean code;
|
||||
|
||||
@JsonCreator
|
||||
EBTurbulenceCategory(Boolean code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public Boolean getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2022 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
|
||||
*
|
||||
* https://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.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
/**
|
||||
* An enum that has an Integer getCode() method.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public enum EITurbulenceCategory {
|
||||
T10(10), T20(20), T30(30), T40(40);
|
||||
|
||||
private final Integer code;
|
||||
|
||||
EITurbulenceCategory(Integer code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2022 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
|
||||
*
|
||||
* https://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.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
/**
|
||||
* An enum that has an @JsonCreator method.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public enum EJsonCreatorTurbulenceCategory {
|
||||
T10("10%"), T20("20%"), T30("30%"), T40("40%");
|
||||
|
||||
private final String code;
|
||||
|
||||
@JsonCreator
|
||||
EJsonCreatorTurbulenceCategory(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2022 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
|
||||
*
|
||||
* https://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.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
/**
|
||||
* An enum that has an String getCode() method.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public enum ETurbulenceCategory {
|
||||
T10("10%"), T20("20%"), T30("30%");
|
||||
|
||||
private final String code;
|
||||
|
||||
ETurbulenceCategory(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@@ -58,13 +58,13 @@ public class Person extends AbstractEntity implements Persistable<Object> {
|
||||
|
||||
public Person() {
|
||||
setId(UUID.randomUUID());
|
||||
setMiddlename("Nick");
|
||||
}
|
||||
|
||||
public Person(String firstname, String lastname) {
|
||||
this();
|
||||
setFirstname(firstname);
|
||||
setLastname(lastname);
|
||||
setMiddlename("Nick");
|
||||
isNew(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -69,10 +69,16 @@ import org.springframework.data.couchbase.core.query.QueryCriteria;
|
||||
import org.springframework.data.couchbase.domain.Address;
|
||||
import org.springframework.data.couchbase.domain.AirlineRepository;
|
||||
import org.springframework.data.couchbase.domain.Airport;
|
||||
import org.springframework.data.couchbase.domain.AirportJsonValue;
|
||||
import org.springframework.data.couchbase.domain.AirportJsonValueRepository;
|
||||
import org.springframework.data.couchbase.domain.AirportJsonValuedObject;
|
||||
import org.springframework.data.couchbase.domain.AirportMini;
|
||||
import org.springframework.data.couchbase.domain.AirportRepository;
|
||||
import org.springframework.data.couchbase.domain.AirportRepositoryScanConsistencyTest;
|
||||
import org.springframework.data.couchbase.domain.Course;
|
||||
import org.springframework.data.couchbase.domain.EITurbulenceCategory;
|
||||
import org.springframework.data.couchbase.domain.EJsonCreatorTurbulenceCategory;
|
||||
import org.springframework.data.couchbase.domain.ETurbulenceCategory;
|
||||
import org.springframework.data.couchbase.domain.Iata;
|
||||
import org.springframework.data.couchbase.domain.NaiveAuditorAware;
|
||||
import org.springframework.data.couchbase.domain.Person;
|
||||
@@ -108,6 +114,7 @@ import com.couchbase.client.core.error.CouchbaseException;
|
||||
import com.couchbase.client.core.error.IndexFailureException;
|
||||
import com.couchbase.client.java.env.ClusterEnvironment;
|
||||
import com.couchbase.client.java.json.JsonArray;
|
||||
import com.couchbase.client.java.json.JsonObject;
|
||||
import com.couchbase.client.java.kv.GetResult;
|
||||
import com.couchbase.client.java.kv.InsertOptions;
|
||||
import com.couchbase.client.java.kv.MutationState;
|
||||
@@ -129,6 +136,8 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
|
||||
|
||||
@Autowired AirportRepository airportRepository;
|
||||
|
||||
@Autowired AirportJsonValueRepository airportJsonValueRepository;
|
||||
|
||||
@Autowired AirlineRepository airlineRepository;
|
||||
|
||||
@Autowired UserRepository userRepository;
|
||||
@@ -316,6 +325,120 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void sdcJsonValue() {
|
||||
AirportJsonValue vie = null;
|
||||
try {
|
||||
vie = new AirportJsonValue("airports::vie", "vie", "low6");
|
||||
vie.setTurbulence1(ETurbulenceCategory.T10);
|
||||
vie.setTurbulence2(ETurbulenceCategory.T20);
|
||||
vie.setTurbulence3(EITurbulenceCategory.T30);
|
||||
vie.setTurbulence4(EJsonCreatorTurbulenceCategory.T40);
|
||||
// sdk/jackson cannot handle vie.setTurbulence5(EBTurbulenceCategory.T);
|
||||
vie.setJvObject(new AirportJsonValuedObject("1st"));
|
||||
|
||||
airportJsonValueRepository.save(vie);
|
||||
|
||||
// check that they were saved as expected
|
||||
GetResult result = couchbaseTemplate.getCouchbaseClientFactory().getCluster().bucket(bucketName())
|
||||
.defaultCollection().get(vie.getId());
|
||||
JsonObject jo = JsonObject.fromJson(result.contentAsBytes());
|
||||
assertEquals(ETurbulenceCategory.T10.getCode(), jo.get("turbulence1"));
|
||||
assertEquals(ETurbulenceCategory.T20.getCode(), jo.get("turbulence2"));
|
||||
assertEquals(EITurbulenceCategory.T30.getCode(), jo.get("turbulence3"));
|
||||
assertEquals(EJsonCreatorTurbulenceCategory.T40.getCode(), jo.get("turbulence4"));
|
||||
// sdk/jackson cannot handle assertEquals(EBTurbulenceCategory.T.getCode(), jo.get("turbulence5"));
|
||||
assertEquals(new AirportJsonValuedObject("1st").getMapped(), jo.get("jvObject"));
|
||||
|
||||
// check that spring-data-couchbase deserializes as expected
|
||||
AirportJsonValue airport = airportJsonValueRepository.findById(vie.getId()).get();
|
||||
System.out.println(airport);
|
||||
assertEquals(ETurbulenceCategory.T10, airport.getTurbulence1());
|
||||
assertEquals(ETurbulenceCategory.T20, airport.getTurbulence2());
|
||||
assertEquals(EITurbulenceCategory.T30, airport.getTurbulence3());
|
||||
assertEquals(EJsonCreatorTurbulenceCategory.T40, airport.getTurbulence4());
|
||||
// sdk/jackson cannot handle assertEquals(EBTurbulenceCategory.T, airport.getTurbulence5());
|
||||
assertEquals(new AirportJsonValuedObject("1st").theValue, airport.getJvObject().theValue);
|
||||
|
||||
// check that java sdk deserializes as expected
|
||||
airport = result.contentAs(AirportJsonValue.class);
|
||||
assertEquals(ETurbulenceCategory.T10, airport.getTurbulence1());
|
||||
assertEquals(ETurbulenceCategory.T20, airport.getTurbulence2());
|
||||
assertEquals(EITurbulenceCategory.T30, airport.getTurbulence3());
|
||||
assertEquals(EJsonCreatorTurbulenceCategory.T40, airport.getTurbulence4());
|
||||
// sdk/jackson cannot handle assertEquals(EBTurbulenceCategory.T, airport.getTurbulence5());
|
||||
assertEquals(new AirportJsonValuedObject("1st").theValue, airport.getJvObject().theValue);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
} finally {
|
||||
try {
|
||||
airportJsonValueRepository.delete(vie);
|
||||
} catch (Exception e) {
|
||||
System.err.println(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void sdkJsonValue() {
|
||||
AirportJsonValue vie = null;
|
||||
try {
|
||||
vie = new AirportJsonValue("airports::vie", "vie", "low6");
|
||||
vie.setTurbulence1(ETurbulenceCategory.T10);
|
||||
vie.setTurbulence2(ETurbulenceCategory.T20);
|
||||
vie.setTurbulence3(EITurbulenceCategory.T30);
|
||||
vie.setTurbulence4(EJsonCreatorTurbulenceCategory.T40);
|
||||
// sdk/jackson cannot handle vie.setTurbulence5(EBTurbulenceCategory.T);
|
||||
vie.setJvObject(new AirportJsonValuedObject("1st"));
|
||||
|
||||
//airportJsonValueRepository.save(vie);
|
||||
couchbaseTemplate.getCouchbaseClientFactory().getCluster().bucket(bucketName())
|
||||
.defaultCollection().upsert(vie.getId(), vie);
|
||||
|
||||
// check that they were saved as expected
|
||||
GetResult result = couchbaseTemplate.getCouchbaseClientFactory().getCluster().bucket(bucketName())
|
||||
.defaultCollection().get(vie.getId());
|
||||
JsonObject jo = JsonObject.fromJson(result.contentAsBytes());
|
||||
assertEquals(ETurbulenceCategory.T10.getCode(), jo.get("turbulence1"));
|
||||
assertEquals(ETurbulenceCategory.T20.getCode(), jo.get("turbulence2"));
|
||||
assertEquals(EITurbulenceCategory.T30.getCode(), jo.get("turbulence3"));
|
||||
assertEquals(EJsonCreatorTurbulenceCategory.T40.getCode(), jo.get("turbulence4"));
|
||||
// sdk/jackson cannot handle assertEquals(EBTurbulenceCategory.T.getCode(), jo.get("turbulence5"));
|
||||
assertEquals(new AirportJsonValuedObject("1st").getMapped(), jo.get("jvObject"));
|
||||
|
||||
// check that spring-data-couchbase deserializes as expected
|
||||
AirportJsonValue airport = airportJsonValueRepository.findById(vie.getId()).get();
|
||||
System.out.println(airport);
|
||||
assertEquals(ETurbulenceCategory.T10, airport.getTurbulence1());
|
||||
assertEquals(ETurbulenceCategory.T20, airport.getTurbulence2());
|
||||
assertEquals(EITurbulenceCategory.T30, airport.getTurbulence3());
|
||||
assertEquals(EJsonCreatorTurbulenceCategory.T40, airport.getTurbulence4());
|
||||
// sdk/jackson cannot handle assertEquals(EBTurbulenceCategory.T, airport.getTurbulence5());
|
||||
assertEquals(new AirportJsonValuedObject("1st").theValue, airport.getJvObject().theValue);
|
||||
|
||||
// check that java sdk deserializes as expected
|
||||
airport = result.contentAs(AirportJsonValue.class);
|
||||
assertEquals(ETurbulenceCategory.T10, airport.getTurbulence1());
|
||||
assertEquals(ETurbulenceCategory.T20, airport.getTurbulence2());
|
||||
assertEquals(EITurbulenceCategory.T30, airport.getTurbulence3());
|
||||
assertEquals(EJsonCreatorTurbulenceCategory.T40, airport.getTurbulence4());
|
||||
// sdk/jackson cannot handle assertEquals(EBTurbulenceCategory.T, airport.getTurbulence5());
|
||||
assertEquals(new AirportJsonValuedObject("1st").theValue, airport.getJvObject().theValue);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
} finally {
|
||||
try {
|
||||
airportJsonValueRepository.delete(vie);
|
||||
} catch (Exception e) {
|
||||
System.err.println(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveNotBoundedRequestPlus() {
|
||||
airportRepository.withOptions(QueryOptions.queryOptions().scanConsistency(REQUEST_PLUS)).deleteAll();
|
||||
@@ -867,11 +990,11 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
|
||||
try {
|
||||
userSubmission.setUsername("updateObject");
|
||||
userSubmissionRepository.save(userSubmission);
|
||||
Course[] courses = new Course[]{ new Course("1", "2", "3"), new Course("4","5","6")};
|
||||
Course[] courses = new Course[] { new Course("1", "2", "3"), new Course("4", "5", "6") };
|
||||
userSubmissionRepository.setNamedCourses(userSubmission.getId(), courses);
|
||||
Optional<UserSubmission> fetched = userSubmissionRepository.findById(userSubmission.getId());
|
||||
assertEquals(courses.length, fetched.get().getCourses().size());
|
||||
for(int i=0; i< courses.length; i++){
|
||||
for (int i = 0; i < courses.length; i++) {
|
||||
assertEquals(courses[i], fetched.get().getCourses().get(i));
|
||||
}
|
||||
} finally {
|
||||
@@ -886,13 +1009,13 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
|
||||
try {
|
||||
userSubmission.setUsername("updateObject");
|
||||
userSubmissionRepository.save(userSubmission);
|
||||
Course[] courses = new Course[]{ new Course("1", "2", "3"), new Course("4","5","6")};
|
||||
Course[] courses = new Course[] { new Course("1", "2", "3"), new Course("4", "5", "6") };
|
||||
userSubmissionRepository.setOrderedCourses(userSubmission.getId(), courses);
|
||||
Optional<UserSubmission> fetched = userSubmissionRepository.findById(userSubmission.getId());
|
||||
assertEquals(courses.length, fetched.get().getCourses().size());
|
||||
for(int i=0; i< courses.length; i++){
|
||||
for (int i = 0; i < courses.length; i++) {
|
||||
assertEquals(courses[i], fetched.get().getCourses().get(i));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
userSubmissionRepository.deleteById(userSubmission.getId());
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ public class CouchbaseRepositoryQueryCollectionIntegrationTests extends Collecti
|
||||
assertEquals(saved, found.get(0), "should have found what was saved");
|
||||
List<UserCol> notfound = userColRepository.withScope(DEFAULT_SCOPE)
|
||||
.withCollection(CollectionIdentifier.DEFAULT_COLLECTION).findByFirstname(user.getFirstname());
|
||||
assertEquals(0, notfound.size(), "should not have found what was saved");
|
||||
assertEquals(0, notfound.size(), "should not have found what was saved "+notfound);
|
||||
} finally {
|
||||
try {
|
||||
userColRepository.withScope(otherScope).withCollection(otherCollection).delete(user);
|
||||
@@ -250,7 +250,7 @@ public class CouchbaseRepositoryQueryCollectionIntegrationTests extends Collecti
|
||||
assertEquals(saved, found.get(0), "should have found what was saved");
|
||||
List<UserCol> notfound = userColRepository.withScope(DEFAULT_SCOPE)
|
||||
.withCollection(CollectionIdentifier.DEFAULT_COLLECTION).findByFirstname(user.getFirstname());
|
||||
assertEquals(0, notfound.size(), "should not have found what was saved");
|
||||
assertEquals(0, notfound.size(), "should not have found what was saved "+notfound);
|
||||
userColRepository.withScope(scopeName).withCollection(collectionName).delete(user);
|
||||
} finally {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user