mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-07-02 00:32:12 +00:00
Fix mapping of property values into a collection.
When reading from Elasticsearch into a property of type Collection<T> (List<T> or Set<T>) the MappingElasticsearchConverter now can read both from the returned JSON: an array of T objects - will put the objects in a corresponding collection a single T object will put the single object into a corrsponding colletcion This is implemented and tested for both: entities where the properties have setters and immutable classes that only provide an all-args constructor. Original Pull Request #2282 Closes #2280 (cherry picked from commit 86634ceb38dbc9d1a3985171a4bf12a6d5912612)
This commit is contained in:
parent
a3ebd8be78
commit
346c5cce58
@ -15,6 +15,12 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.elasticsearch.core.convert;
|
package org.springframework.data.elasticsearch.core.convert;
|
||||||
|
|
||||||
|
import java.time.temporal.TemporalAccessor;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
@ -47,16 +53,7 @@ import org.springframework.data.mapping.Parameter;
|
|||||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||||
import org.springframework.data.mapping.SimplePropertyHandler;
|
import org.springframework.data.mapping.SimplePropertyHandler;
|
||||||
import org.springframework.data.mapping.context.MappingContext;
|
import org.springframework.data.mapping.context.MappingContext;
|
||||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
import org.springframework.data.mapping.model.*;
|
||||||
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
|
|
||||||
import org.springframework.data.mapping.model.EntityInstantiator;
|
|
||||||
import org.springframework.data.mapping.model.EntityInstantiators;
|
|
||||||
import org.springframework.data.mapping.model.ParameterValueProvider;
|
|
||||||
import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
|
|
||||||
import org.springframework.data.mapping.model.PropertyValueProvider;
|
|
||||||
import org.springframework.data.mapping.model.SpELContext;
|
|
||||||
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
|
|
||||||
import org.springframework.data.mapping.model.SpELExpressionParameterValueProvider;
|
|
||||||
import org.springframework.data.util.ClassTypeInformation;
|
import org.springframework.data.util.ClassTypeInformation;
|
||||||
import org.springframework.data.util.TypeInformation;
|
import org.springframework.data.util.TypeInformation;
|
||||||
import org.springframework.format.datetime.DateFormatterRegistrar;
|
import org.springframework.format.datetime.DateFormatterRegistrar;
|
||||||
@ -66,12 +63,6 @@ import org.springframework.util.ClassUtils;
|
|||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
|
|
||||||
import java.time.temporal.TemporalAccessor;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Elasticsearch specific {@link org.springframework.data.convert.EntityConverter} implementation based on domain type
|
* Elasticsearch specific {@link org.springframework.data.convert.EntityConverter} implementation based on domain type
|
||||||
* {@link ElasticsearchPersistentEntity metadata}.
|
* {@link ElasticsearchPersistentEntity metadata}.
|
||||||
@ -92,15 +83,12 @@ import java.util.stream.Collectors;
|
|||||||
public class MappingElasticsearchConverter
|
public class MappingElasticsearchConverter
|
||||||
implements ElasticsearchConverter, ApplicationContextAware, InitializingBean {
|
implements ElasticsearchConverter, ApplicationContextAware, InitializingBean {
|
||||||
|
|
||||||
private static final String INCOMPATIBLE_TYPES =
|
private static final String INCOMPATIBLE_TYPES = "Cannot convert %1$s of type %2$s into an instance of %3$s! Implement a custom Converter<%2$s, %3$s> and register it with the CustomConversions.";
|
||||||
"Cannot convert %1$s of type %2$s into an instance of %3$s! Implement a custom Converter<%2$s, %3$s> and register it with the CustomConversions.";
|
private static final String INVALID_TYPE_TO_READ = "Expected to read Document %s into type %s but didn't find a PersistentEntity for the latter!";
|
||||||
private static final String INVALID_TYPE_TO_READ =
|
|
||||||
"Expected to read Document %s into type %s but didn't find a PersistentEntity for the latter!";
|
|
||||||
|
|
||||||
private static final Log LOGGER = LogFactory.getLog(MappingElasticsearchConverter.class);
|
private static final Log LOGGER = LogFactory.getLog(MappingElasticsearchConverter.class);
|
||||||
|
|
||||||
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty>
|
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
|
||||||
mappingContext;
|
|
||||||
private final GenericConversionService conversionService;
|
private final GenericConversionService conversionService;
|
||||||
private CustomConversions conversions = new ElasticsearchCustomConversions(Collections.emptyList());
|
private CustomConversions conversions = new ElasticsearchCustomConversions(Collections.emptyList());
|
||||||
private final SpELContext spELContext = new SpELContext(new MapAccessor());
|
private final SpELContext spELContext = new SpELContext(new MapAccessor());
|
||||||
@ -142,8 +130,8 @@ public class MappingElasticsearchConverter
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the {@link CustomConversions} to be applied during the mapping process. <br /> Conversions are registered
|
* Set the {@link CustomConversions} to be applied during the mapping process. <br />
|
||||||
* after {@link #afterPropertiesSet() bean initialization}.
|
* Conversions are registered after {@link #afterPropertiesSet() bean initialization}.
|
||||||
*
|
*
|
||||||
* @param conversions must not be {@literal null}.
|
* @param conversions must not be {@literal null}.
|
||||||
*/
|
*/
|
||||||
@ -169,8 +157,7 @@ public class MappingElasticsearchConverter
|
|||||||
@Override
|
@Override
|
||||||
public <R> R read(Class<R> type, Document source) {
|
public <R> R read(Class<R> type, Document source) {
|
||||||
|
|
||||||
Reader reader =
|
Reader reader = new Reader(mappingContext, conversionService, conversions, typeMapper, spELContext, instantiators);
|
||||||
new Reader(mappingContext, conversionService, conversions, typeMapper, spELContext, instantiators);
|
|
||||||
return reader.read(type, source);
|
return reader.read(type, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,8 +175,7 @@ public class MappingElasticsearchConverter
|
|||||||
*/
|
*/
|
||||||
private static class Base {
|
private static class Base {
|
||||||
|
|
||||||
protected final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty>
|
protected final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
|
||||||
mappingContext;
|
|
||||||
protected final ElasticsearchTypeMapper typeMapper;
|
protected final ElasticsearchTypeMapper typeMapper;
|
||||||
protected final GenericConversionService conversionService;
|
protected final GenericConversionService conversionService;
|
||||||
protected final CustomConversions conversions;
|
protected final CustomConversions conversions;
|
||||||
@ -197,8 +183,7 @@ public class MappingElasticsearchConverter
|
|||||||
|
|
||||||
private Base(
|
private Base(
|
||||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext,
|
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext,
|
||||||
GenericConversionService conversionService, CustomConversions conversions,
|
GenericConversionService conversionService, CustomConversions conversions, ElasticsearchTypeMapper typeMapper) {
|
||||||
ElasticsearchTypeMapper typeMapper) {
|
|
||||||
this.mappingContext = mappingContext;
|
this.mappingContext = mappingContext;
|
||||||
this.conversionService = conversionService;
|
this.conversionService = conversionService;
|
||||||
this.conversions = conversions;
|
this.conversions = conversions;
|
||||||
@ -207,7 +192,7 @@ public class MappingElasticsearchConverter
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to do the actual writing. The methods originally were in the MappingElasticsearchConverter class, but are
|
* Class to do the actual reading. The methods originally were in the MappingElasticsearchConverter class, but are
|
||||||
* refactored to allow for keeping state during the conversion of an object.
|
* refactored to allow for keeping state during the conversion of an object.
|
||||||
*/
|
*/
|
||||||
private static class Reader extends Base {
|
private static class Reader extends Base {
|
||||||
@ -217,8 +202,7 @@ public class MappingElasticsearchConverter
|
|||||||
|
|
||||||
public Reader(
|
public Reader(
|
||||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext,
|
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext,
|
||||||
GenericConversionService conversionService, CustomConversions conversions,
|
GenericConversionService conversionService, CustomConversions conversions, ElasticsearchTypeMapper typeMapper,
|
||||||
ElasticsearchTypeMapper typeMapper,
|
|
||||||
SpELContext spELContext, EntityInstantiators instantiators) {
|
SpELContext spELContext, EntityInstantiators instantiators) {
|
||||||
|
|
||||||
super(mappingContext, conversionService, conversions, typeMapper);
|
super(mappingContext, conversionService, conversions, typeMapper);
|
||||||
@ -227,10 +211,17 @@ public class MappingElasticsearchConverter
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
/**
|
||||||
|
* Reads the given source into the given type.
|
||||||
|
*
|
||||||
|
* @param type they type to convert the given source to.
|
||||||
|
* @param source the source to create an object of the given type from.
|
||||||
|
* @return the object that was read
|
||||||
|
*/
|
||||||
<R> R read(Class<R> type, Document source) {
|
<R> R read(Class<R> type, Document source) {
|
||||||
|
|
||||||
TypeInformation<R> typeHint = ClassTypeInformation.from((Class<R>) ClassUtils.getUserClass(type));
|
TypeInformation<R> typeInformation = ClassTypeInformation.from((Class<R>) ClassUtils.getUserClass(type));
|
||||||
R r = read(typeHint, source);
|
R r = read(typeInformation, source);
|
||||||
|
|
||||||
if (r == null) {
|
if (r == null) {
|
||||||
throw new ConversionException("could not convert into object of class " + type);
|
throw new ConversionException("could not convert into object of class " + type);
|
||||||
@ -241,11 +232,11 @@ public class MappingElasticsearchConverter
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private <R> R read(TypeInformation<R> type, Map<String, Object> source) {
|
private <R> R read(TypeInformation<R> typeInformation, Map<String, Object> source) {
|
||||||
|
|
||||||
Assert.notNull(source, "Source must not be null!");
|
Assert.notNull(source, "Source must not be null!");
|
||||||
|
|
||||||
TypeInformation<? extends R> typeToUse = typeMapper.readType(source, type);
|
TypeInformation<? extends R> typeToUse = typeMapper.readType(source, typeInformation);
|
||||||
Class<? extends R> rawType = typeToUse.getType();
|
Class<? extends R> rawType = typeToUse.getType();
|
||||||
|
|
||||||
if (conversions.hasCustomReadTarget(source.getClass(), rawType)) {
|
if (conversions.hasCustomReadTarget(source.getClass(), rawType)) {
|
||||||
@ -263,8 +254,8 @@ public class MappingElasticsearchConverter
|
|||||||
if (typeToUse.equals(ClassTypeInformation.OBJECT)) {
|
if (typeToUse.equals(ClassTypeInformation.OBJECT)) {
|
||||||
return (R) source;
|
return (R) source;
|
||||||
}
|
}
|
||||||
// Retrieve persistent entity info
|
|
||||||
|
|
||||||
|
// Retrieve persistent entity info
|
||||||
ElasticsearchPersistentEntity<?> entity = mappingContext.getPersistentEntity(typeToUse);
|
ElasticsearchPersistentEntity<?> entity = mappingContext.getPersistentEntity(typeToUse);
|
||||||
|
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
@ -308,8 +299,7 @@ public class MappingElasticsearchConverter
|
|||||||
map.put(key, read(defaultedValueType, (Map<String, Object>) value));
|
map.put(key, read(defaultedValueType, (Map<String, Object>) value));
|
||||||
} else if (value instanceof List) {
|
} else if (value instanceof List) {
|
||||||
map.put(key,
|
map.put(key,
|
||||||
readCollectionOrArray(valueType != null ? valueType : ClassTypeInformation.LIST,
|
readCollectionOrArray(valueType != null ? valueType : ClassTypeInformation.LIST, (List<Object>) value));
|
||||||
(List<Object>) value));
|
|
||||||
} else {
|
} else {
|
||||||
map.put(key, getPotentiallyConvertedSimpleRead(value, rawValueType));
|
map.put(key, getPotentiallyConvertedSimpleRead(value, rawValueType));
|
||||||
}
|
}
|
||||||
@ -340,8 +330,7 @@ public class MappingElasticsearchConverter
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
ElasticsearchPropertyValueProvider valueProvider =
|
ElasticsearchPropertyValueProvider valueProvider = new ElasticsearchPropertyValueProvider(accessor, evaluator);
|
||||||
new ElasticsearchPropertyValueProvider(accessor, evaluator);
|
|
||||||
R result = readProperties(targetEntity, instance, valueProvider);
|
R result = readProperties(targetEntity, instance, valueProvider);
|
||||||
|
|
||||||
if (source instanceof Document) {
|
if (source instanceof Document) {
|
||||||
@ -351,8 +340,7 @@ public class MappingElasticsearchConverter
|
|||||||
PersistentPropertyAccessor<R> propertyAccessor = new ConvertingPropertyAccessor<>(
|
PersistentPropertyAccessor<R> propertyAccessor = new ConvertingPropertyAccessor<>(
|
||||||
targetEntity.getPropertyAccessor(result), conversionService);
|
targetEntity.getPropertyAccessor(result), conversionService);
|
||||||
// Only deal with String because ES generated Ids are strings !
|
// Only deal with String because ES generated Ids are strings !
|
||||||
if (idProperty != null && idProperty.isWritable() &&
|
if (idProperty != null && idProperty.isWritable() && idProperty.getType().isAssignableFrom(String.class)) {
|
||||||
idProperty.getType().isAssignableFrom(String.class)) {
|
|
||||||
propertyAccessor.setProperty(idProperty, document.getId());
|
propertyAccessor.setProperty(idProperty, document.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -371,8 +359,7 @@ public class MappingElasticsearchConverter
|
|||||||
|
|
||||||
if (targetEntity.hasSeqNoPrimaryTermProperty() && document.hasSeqNo() && document.hasPrimaryTerm()) {
|
if (targetEntity.hasSeqNoPrimaryTermProperty() && document.hasSeqNo() && document.hasPrimaryTerm()) {
|
||||||
if (isAssignedSeqNo(document.getSeqNo()) && isAssignedPrimaryTerm(document.getPrimaryTerm())) {
|
if (isAssignedSeqNo(document.getSeqNo()) && isAssignedPrimaryTerm(document.getPrimaryTerm())) {
|
||||||
SeqNoPrimaryTerm seqNoPrimaryTerm =
|
SeqNoPrimaryTerm seqNoPrimaryTerm = new SeqNoPrimaryTerm(document.getSeqNo(), document.getPrimaryTerm());
|
||||||
new SeqNoPrimaryTerm(document.getSeqNo(), document.getPrimaryTerm());
|
|
||||||
ElasticsearchPersistentProperty property = targetEntity.getRequiredSeqNoPrimaryTermProperty();
|
ElasticsearchPersistentProperty property = targetEntity.getRequiredSeqNoPrimaryTermProperty();
|
||||||
targetEntity.getPropertyAccessor(result).setProperty(property, seqNoPrimaryTerm);
|
targetEntity.getPropertyAccessor(result).setProperty(property, seqNoPrimaryTerm);
|
||||||
}
|
}
|
||||||
@ -385,7 +372,6 @@ public class MappingElasticsearchConverter
|
|||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ParameterValueProvider<ElasticsearchPersistentProperty> getParameterProvider(
|
private ParameterValueProvider<ElasticsearchPersistentProperty> getParameterProvider(
|
||||||
@ -395,12 +381,10 @@ public class MappingElasticsearchConverter
|
|||||||
|
|
||||||
// TODO: Support for non-static inner classes via ObjectPath
|
// TODO: Support for non-static inner classes via ObjectPath
|
||||||
// noinspection ConstantConditions
|
// noinspection ConstantConditions
|
||||||
PersistentEntityParameterValueProvider<ElasticsearchPersistentProperty> parameterProvider =
|
PersistentEntityParameterValueProvider<ElasticsearchPersistentProperty> parameterProvider = new PersistentEntityParameterValueProvider<>(
|
||||||
new PersistentEntityParameterValueProvider<>(
|
|
||||||
entity, provider, null);
|
entity, provider, null);
|
||||||
|
|
||||||
return new ConverterAwareSpELExpressionParameterValueProvider(evaluator, conversionService,
|
return new ConverterAwareSpELExpressionParameterValueProvider(evaluator, conversionService, parameterProvider);
|
||||||
parameterProvider);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAssignedSeqNo(long seqNo) {
|
private boolean isAssignedSeqNo(long seqNo) {
|
||||||
@ -414,8 +398,7 @@ public class MappingElasticsearchConverter
|
|||||||
protected <R> R readProperties(ElasticsearchPersistentEntity<?> entity, R instance,
|
protected <R> R readProperties(ElasticsearchPersistentEntity<?> entity, R instance,
|
||||||
ElasticsearchPropertyValueProvider valueProvider) {
|
ElasticsearchPropertyValueProvider valueProvider) {
|
||||||
|
|
||||||
PersistentPropertyAccessor<R> accessor =
|
PersistentPropertyAccessor<R> accessor = new ConvertingPropertyAccessor<>(entity.getPropertyAccessor(instance),
|
||||||
new ConvertingPropertyAccessor<>(entity.getPropertyAccessor(instance),
|
|
||||||
conversionService);
|
conversionService);
|
||||||
|
|
||||||
for (ElasticsearchPersistentProperty prop : entity) {
|
for (ElasticsearchPersistentProperty prop : entity) {
|
||||||
@ -478,15 +461,46 @@ public class MappingElasticsearchConverter
|
|||||||
} else if (value.getClass().isArray()) {
|
} else if (value.getClass().isArray()) {
|
||||||
return (T) readCollectionOrArray(type, Arrays.asList((Object[]) value));
|
return (T) readCollectionOrArray(type, Arrays.asList((Object[]) value));
|
||||||
} else if (value instanceof Map) {
|
} else if (value instanceof Map) {
|
||||||
|
|
||||||
|
TypeInformation<?> collectionComponentType = getCollectionComponentType(type);
|
||||||
|
if (collectionComponentType != null) {
|
||||||
|
Object o = read(collectionComponentType, (Map<String, Object>) value);
|
||||||
|
return getCollectionWithSingleElement(type, collectionComponentType, o);
|
||||||
|
}
|
||||||
return (T) read(type, (Map<String, Object>) value);
|
return (T) read(type, (Map<String, Object>) value);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
TypeInformation<?> collectionComponentType = getCollectionComponentType(type);
|
||||||
|
if (collectionComponentType != null
|
||||||
|
&& collectionComponentType.isAssignableFrom(ClassTypeInformation.from(value.getClass()))) {
|
||||||
|
Object o = getPotentiallyConvertedSimpleRead(value, collectionComponentType);
|
||||||
|
return getCollectionWithSingleElement(type, collectionComponentType, o);
|
||||||
|
}
|
||||||
|
|
||||||
return (T) getPotentiallyConvertedSimpleRead(value, rawType);
|
return (T) getPotentiallyConvertedSimpleRead(value, rawType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static <T> T getCollectionWithSingleElement(TypeInformation<?> collectionType,
|
||||||
|
TypeInformation<?> componentType, Object element) {
|
||||||
|
Collection<Object> collection = CollectionFactory.createCollection(collectionType.getType(),
|
||||||
|
componentType.getType(), 1);
|
||||||
|
collection.add(element);
|
||||||
|
return (T) collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param type the type to check
|
||||||
|
* @return true if type is a collectoin, null otherwise,
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
TypeInformation<?> getCollectionComponentType(TypeInformation<?> type) {
|
||||||
|
return type.isCollectionLike() ? type.getComponentType() : null;
|
||||||
|
}
|
||||||
|
|
||||||
private Object propertyConverterRead(ElasticsearchPersistentProperty property, Object source) {
|
private Object propertyConverterRead(ElasticsearchPersistentProperty property, Object source) {
|
||||||
PropertyValueConverter propertyValueConverter =
|
PropertyValueConverter propertyValueConverter = Objects.requireNonNull(property.getPropertyValueConverter());
|
||||||
Objects.requireNonNull(property.getPropertyValueConverter());
|
|
||||||
|
|
||||||
if (source instanceof String[]) {
|
if (source instanceof String[]) {
|
||||||
// convert to a List
|
// convert to a List
|
||||||
@ -663,8 +677,7 @@ public class MappingElasticsearchConverter
|
|||||||
* @param delegate must not be {@literal null}.
|
* @param delegate must not be {@literal null}.
|
||||||
*/
|
*/
|
||||||
public ConverterAwareSpELExpressionParameterValueProvider(SpELExpressionEvaluator evaluator,
|
public ConverterAwareSpELExpressionParameterValueProvider(SpELExpressionEvaluator evaluator,
|
||||||
ConversionService conversionService,
|
ConversionService conversionService, ParameterValueProvider<ElasticsearchPersistentProperty> delegate) {
|
||||||
ParameterValueProvider<ElasticsearchPersistentProperty> delegate) {
|
|
||||||
|
|
||||||
super(evaluator, conversionService, delegate);
|
super(evaluator, conversionService, delegate);
|
||||||
}
|
}
|
||||||
@ -701,8 +714,7 @@ public class MappingElasticsearchConverter
|
|||||||
|
|
||||||
public Writer(
|
public Writer(
|
||||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext,
|
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext,
|
||||||
GenericConversionService conversionService, CustomConversions conversions,
|
GenericConversionService conversionService, CustomConversions conversions, ElasticsearchTypeMapper typeMapper) {
|
||||||
ElasticsearchTypeMapper typeMapper) {
|
|
||||||
super(mappingContext, conversionService, conversions, typeMapper);
|
super(mappingContext, conversionService, conversions, typeMapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -787,8 +799,7 @@ public class MappingElasticsearchConverter
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (null == entity) {
|
if (null == entity) {
|
||||||
throw new MappingException(
|
throw new MappingException("No mapping metadata found for entity of type " + source.getClass().getName());
|
||||||
"No mapping metadata found for entity of type " + source.getClass().getName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PersistentPropertyAccessor<?> accessor = entity.getPropertyAccessor(source);
|
PersistentPropertyAccessor<?> accessor = entity.getPropertyAccessor(source);
|
||||||
@ -837,8 +848,7 @@ public class MappingElasticsearchConverter
|
|||||||
sink.put(simpleKey, getPotentiallyConvertedSimpleWrite(value, Object.class));
|
sink.put(simpleKey, getPotentiallyConvertedSimpleWrite(value, Object.class));
|
||||||
} else if (value instanceof Collection || value.getClass().isArray()) {
|
} else if (value instanceof Collection || value.getClass().isArray()) {
|
||||||
sink.put(simpleKey,
|
sink.put(simpleKey,
|
||||||
writeCollectionInternal(asCollection(value), propertyType.getMapValueType(),
|
writeCollectionInternal(asCollection(value), propertyType.getMapValueType(), new ArrayList<>()));
|
||||||
new ArrayList<>()));
|
|
||||||
} else {
|
} else {
|
||||||
Map<String, Object> document = Document.create();
|
Map<String, Object> document = Document.create();
|
||||||
TypeInformation<?> valueTypeInfo = propertyType.isMap() ? propertyType.getMapValueType()
|
TypeInformation<?> valueTypeInfo = propertyType.isMap() ? propertyType.getMapValueType()
|
||||||
@ -990,9 +1000,9 @@ public class MappingElasticsearchConverter
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds custom typeInformation information to the given {@link Map} if necessary. That is if the value is not
|
* Adds custom typeInformation information to the given {@link Map} if necessary. That is if the value is not the
|
||||||
* the same as the one given. This is usually the case if you store a subtype of the actual declared
|
* same as the one given. This is usually the case if you store a subtype of the actual declared typeInformation of
|
||||||
* typeInformation of the property.
|
* the property.
|
||||||
*
|
*
|
||||||
* @param source must not be {@literal null}.
|
* @param source must not be {@literal null}.
|
||||||
* @param sink must not be {@literal null}.
|
* @param sink must not be {@literal null}.
|
||||||
@ -1044,9 +1054,9 @@ public class MappingElasticsearchConverter
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether we have a custom conversion registered for the given value into an arbitrary simple
|
* Checks whether we have a custom conversion registered for the given value into an arbitrary simple Elasticsearch
|
||||||
* Elasticsearch type. Returns the converted value if so. If not, we perform special enum handling or simply
|
* type. Returns the converted value if so. If not, we perform special enum handling or simply return the value as
|
||||||
* return the value as is.
|
* is.
|
||||||
*
|
*
|
||||||
* @param value value to convert
|
* @param value value to convert
|
||||||
*/
|
*/
|
||||||
@ -1086,8 +1096,7 @@ public class MappingElasticsearchConverter
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Object propertyConverterWrite(ElasticsearchPersistentProperty property, Object value) {
|
private Object propertyConverterWrite(ElasticsearchPersistentProperty property, Object value) {
|
||||||
PropertyValueConverter propertyValueConverter =
|
PropertyValueConverter propertyValueConverter = Objects.requireNonNull(property.getPropertyValueConverter());
|
||||||
Objects.requireNonNull(property.getPropertyValueConverter());
|
|
||||||
|
|
||||||
if (value instanceof List) {
|
if (value instanceof List) {
|
||||||
value = ((List<?>) value).stream().map(propertyValueConverter::write).collect(Collectors.toList());
|
value = ((List<?>) value).stream().map(propertyValueConverter::write).collect(Collectors.toList());
|
||||||
@ -1106,8 +1115,7 @@ public class MappingElasticsearchConverter
|
|||||||
* @param property must not be {@literal null}.
|
* @param property must not be {@literal null}.
|
||||||
*/
|
*/
|
||||||
protected List<Object> createCollection(Collection<?> collection, ElasticsearchPersistentProperty property) {
|
protected List<Object> createCollection(Collection<?> collection, ElasticsearchPersistentProperty property) {
|
||||||
return writeCollectionInternal(collection, property.getTypeInformation(),
|
return writeCollectionInternal(collection, property.getTypeInformation(), new ArrayList<>(collection.size()));
|
||||||
new ArrayList<>(collection.size()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1148,17 +1156,18 @@ public class MappingElasticsearchConverter
|
|||||||
|
|
||||||
Assert.notNull(query, "query must not be null");
|
Assert.notNull(query, "query must not be null");
|
||||||
|
|
||||||
if (domainClass != null) {
|
if (domainClass == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
updateFieldsAndSourceFilter(query, domainClass);
|
updatePropertiesInFieldsAndSourceFilter(query, domainClass);
|
||||||
|
|
||||||
if (query instanceof CriteriaQuery) {
|
if (query instanceof CriteriaQuery) {
|
||||||
updateCriteriaQuery((CriteriaQuery) query, domainClass);
|
updatePropertiesInCriteriaQuery((CriteriaQuery) query, domainClass);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateFieldsAndSourceFilter(Query query, Class<?> domainClass) {
|
private void updatePropertiesInFieldsAndSourceFilter(Query query, Class<?> domainClass) {
|
||||||
|
|
||||||
ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(domainClass);
|
ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(domainClass);
|
||||||
|
|
||||||
@ -1196,14 +1205,22 @@ public class MappingElasticsearchConverter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> updateFieldNames(List<String> fields, ElasticsearchPersistentEntity<?> persistentEntity) {
|
/**
|
||||||
return fields.stream().map(fieldName -> {
|
* relaces the fieldName with the property name of a property of the persistentEntity with the corresponding
|
||||||
|
* fieldname. If no such property exists, the original fieldName is kept.
|
||||||
|
*
|
||||||
|
* @param fieldNames list of fieldnames
|
||||||
|
* @param persistentEntity the persistent entity to check
|
||||||
|
* @return an updated list of field names
|
||||||
|
*/
|
||||||
|
private List<String> updateFieldNames(List<String> fieldNames, ElasticsearchPersistentEntity<?> persistentEntity) {
|
||||||
|
return fieldNames.stream().map(fieldName -> {
|
||||||
ElasticsearchPersistentProperty persistentProperty = persistentEntity.getPersistentProperty(fieldName);
|
ElasticsearchPersistentProperty persistentProperty = persistentEntity.getPersistentProperty(fieldName);
|
||||||
return persistentProperty != null ? persistentProperty.getFieldName() : fieldName;
|
return persistentProperty != null ? persistentProperty.getFieldName() : fieldName;
|
||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateCriteriaQuery(CriteriaQuery criteriaQuery, Class<?> domainClass) {
|
private void updatePropertiesInCriteriaQuery(CriteriaQuery criteriaQuery, Class<?> domainClass) {
|
||||||
|
|
||||||
Assert.notNull(criteriaQuery, "criteriaQuery must not be null");
|
Assert.notNull(criteriaQuery, "criteriaQuery must not be null");
|
||||||
Assert.notNull(domainClass, "domainClass must not be null");
|
Assert.notNull(domainClass, "domainClass must not be null");
|
||||||
@ -1212,17 +1229,17 @@ public class MappingElasticsearchConverter
|
|||||||
|
|
||||||
if (persistentEntity != null) {
|
if (persistentEntity != null) {
|
||||||
for (Criteria chainedCriteria : criteriaQuery.getCriteria().getCriteriaChain()) {
|
for (Criteria chainedCriteria : criteriaQuery.getCriteria().getCriteriaChain()) {
|
||||||
updateCriteria(chainedCriteria, persistentEntity);
|
updatePropertiesInCriteria(chainedCriteria, persistentEntity);
|
||||||
}
|
}
|
||||||
for (Criteria subCriteria : criteriaQuery.getCriteria().getSubCriteria()) {
|
for (Criteria subCriteria : criteriaQuery.getCriteria().getSubCriteria()) {
|
||||||
for (Criteria chainedCriteria : subCriteria.getCriteriaChain()) {
|
for (Criteria chainedCriteria : subCriteria.getCriteriaChain()) {
|
||||||
updateCriteria(chainedCriteria, persistentEntity);
|
updatePropertiesInCriteria(chainedCriteria, persistentEntity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateCriteria(Criteria criteria, ElasticsearchPersistentEntity<?> persistentEntity) {
|
private void updatePropertiesInCriteria(Criteria criteria, ElasticsearchPersistentEntity<?> persistentEntity) {
|
||||||
|
|
||||||
Field field = criteria.getField();
|
Field field = criteria.getField();
|
||||||
|
|
||||||
|
@ -33,7 +33,10 @@ import java.util.HashMap;
|
|||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.intellij.lang.annotations.Language;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
@ -623,7 +626,8 @@ public class MappingElasticsearchConverterUnitTests {
|
|||||||
assertThat(target.address).isEqualTo(bigBunsCafe);
|
assertThat(target.address).isEqualTo(bigBunsCafe);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // DATAES-716
|
@Test
|
||||||
|
// DATAES-716
|
||||||
void shouldWriteLocalDate() throws JSONException {
|
void shouldWriteLocalDate() throws JSONException {
|
||||||
Person person = new Person();
|
Person person = new Person();
|
||||||
person.setId("4711");
|
person.setId("4711");
|
||||||
@ -665,7 +669,8 @@ public class MappingElasticsearchConverterUnitTests {
|
|||||||
assertEquals(expected, json, false);
|
assertEquals(expected, json, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // DATAES-716
|
@Test
|
||||||
|
// DATAES-716
|
||||||
void shouldReadLocalDate() {
|
void shouldReadLocalDate() {
|
||||||
Document document = Document.create();
|
Document document = Document.create();
|
||||||
document.put("id", "4711");
|
document.put("id", "4711");
|
||||||
@ -695,7 +700,8 @@ public class MappingElasticsearchConverterUnitTests {
|
|||||||
assertThat(entity.getDates()).hasSize(2).containsExactly(LocalDate.of(2020, 9, 15), LocalDate.of(2019, 5, 1));
|
assertThat(entity.getDates()).hasSize(2).containsExactly(LocalDate.of(2020, 9, 15), LocalDate.of(2019, 5, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // DATAES-763
|
@Test
|
||||||
|
// DATAES-763
|
||||||
void writeEntityWithMapDataType() {
|
void writeEntityWithMapDataType() {
|
||||||
|
|
||||||
Notification notification = new Notification();
|
Notification notification = new Notification();
|
||||||
@ -712,7 +718,8 @@ public class MappingElasticsearchConverterUnitTests {
|
|||||||
assertThat(document).isEqualTo(notificationAsMap);
|
assertThat(document).isEqualTo(notificationAsMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // DATAES-763
|
@Test
|
||||||
|
// DATAES-763
|
||||||
void readEntityWithMapDataType() {
|
void readEntityWithMapDataType() {
|
||||||
|
|
||||||
Document document = Document.create();
|
Document document = Document.create();
|
||||||
@ -729,7 +736,8 @@ public class MappingElasticsearchConverterUnitTests {
|
|||||||
assertThat(notification.params.get("content")).isNull();
|
assertThat(notification.params.get("content")).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // DATAES-795
|
@Test
|
||||||
|
// DATAES-795
|
||||||
void readGenericMapWithSimpleTypes() {
|
void readGenericMapWithSimpleTypes() {
|
||||||
Map<String, Object> mapWithSimpleValues = new HashMap<>();
|
Map<String, Object> mapWithSimpleValues = new HashMap<>();
|
||||||
mapWithSimpleValues.put("int", 1);
|
mapWithSimpleValues.put("int", 1);
|
||||||
@ -743,7 +751,8 @@ public class MappingElasticsearchConverterUnitTests {
|
|||||||
assertThat(wrapper.getSchemaLessObject()).isEqualTo(mapWithSimpleValues);
|
assertThat(wrapper.getSchemaLessObject()).isEqualTo(mapWithSimpleValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // DATAES-797
|
@Test
|
||||||
|
// DATAES-797
|
||||||
void readGenericListWithMaps() {
|
void readGenericListWithMaps() {
|
||||||
Map<String, Object> simpleMap = new HashMap<>();
|
Map<String, Object> simpleMap = new HashMap<>();
|
||||||
simpleMap.put("int", 1);
|
simpleMap.put("int", 1);
|
||||||
@ -761,7 +770,8 @@ public class MappingElasticsearchConverterUnitTests {
|
|||||||
assertThat(wrapper.getSchemaLessObject()).isEqualTo(mapWithSimpleList);
|
assertThat(wrapper.getSchemaLessObject()).isEqualTo(mapWithSimpleList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // DATAES-799
|
@Test
|
||||||
|
// DATAES-799
|
||||||
void shouldNotWriteSeqNoPrimaryTermProperty() {
|
void shouldNotWriteSeqNoPrimaryTermProperty() {
|
||||||
EntityWithSeqNoPrimaryTerm entity = new EntityWithSeqNoPrimaryTerm();
|
EntityWithSeqNoPrimaryTerm entity = new EntityWithSeqNoPrimaryTerm();
|
||||||
entity.seqNoPrimaryTerm = new SeqNoPrimaryTerm(1L, 2L);
|
entity.seqNoPrimaryTerm = new SeqNoPrimaryTerm(1L, 2L);
|
||||||
@ -772,7 +782,8 @@ public class MappingElasticsearchConverterUnitTests {
|
|||||||
assertThat(document).doesNotContainKey("seqNoPrimaryTerm");
|
assertThat(document).doesNotContainKey("seqNoPrimaryTerm");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // DATAES-799
|
@Test
|
||||||
|
// DATAES-799
|
||||||
void shouldNotReadSeqNoPrimaryTermProperty() {
|
void shouldNotReadSeqNoPrimaryTermProperty() {
|
||||||
Document document = Document.create().append("seqNoPrimaryTerm", emptyMap());
|
Document document = Document.create().append("seqNoPrimaryTerm", emptyMap());
|
||||||
|
|
||||||
@ -781,7 +792,8 @@ public class MappingElasticsearchConverterUnitTests {
|
|||||||
assertThat(entity.seqNoPrimaryTerm).isNull();
|
assertThat(entity.seqNoPrimaryTerm).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // DATAES-845
|
@Test
|
||||||
|
// DATAES-845
|
||||||
void shouldWriteCollectionsWithNullValues() throws JSONException {
|
void shouldWriteCollectionsWithNullValues() throws JSONException {
|
||||||
EntityWithListProperty entity = new EntityWithListProperty();
|
EntityWithListProperty entity = new EntityWithListProperty();
|
||||||
entity.setId("42");
|
entity.setId("42");
|
||||||
@ -798,7 +810,8 @@ public class MappingElasticsearchConverterUnitTests {
|
|||||||
assertEquals(expected, json, false);
|
assertEquals(expected, json, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // DATAES-857
|
@Test
|
||||||
|
// DATAES-857
|
||||||
void shouldWriteEntityWithListOfGeoPoints() throws JSONException {
|
void shouldWriteEntityWithListOfGeoPoints() throws JSONException {
|
||||||
|
|
||||||
GeoPointListEntity entity = new GeoPointListEntity();
|
GeoPointListEntity entity = new GeoPointListEntity();
|
||||||
@ -827,7 +840,8 @@ public class MappingElasticsearchConverterUnitTests {
|
|||||||
assertEquals(expected, json, false);
|
assertEquals(expected, json, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // DATAES-857
|
@Test
|
||||||
|
// DATAES-857
|
||||||
void shouldReadEntityWithListOfGeoPoints() {
|
void shouldReadEntityWithListOfGeoPoints() {
|
||||||
|
|
||||||
String json = "{\n" + //
|
String json = "{\n" + //
|
||||||
@ -852,7 +866,8 @@ public class MappingElasticsearchConverterUnitTests {
|
|||||||
assertThat(entity.locations).containsExactly(new GeoPoint(12.34, 23.45), new GeoPoint(34.56, 45.67));
|
assertThat(entity.locations).containsExactly(new GeoPoint(12.34, 23.45), new GeoPoint(34.56, 45.67));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // DATAES-865
|
@Test
|
||||||
|
// DATAES-865
|
||||||
void shouldWriteEntityWithMapAsObject() throws JSONException {
|
void shouldWriteEntityWithMapAsObject() throws JSONException {
|
||||||
|
|
||||||
Map<String, Object> map = new LinkedHashMap<>();
|
Map<String, Object> map = new LinkedHashMap<>();
|
||||||
@ -1509,6 +1524,305 @@ public class MappingElasticsearchConverterUnitTests {
|
|||||||
mappingElasticsearchConverter.updateQuery(query, EntityWithCustomValueConverters.class);
|
mappingElasticsearchConverter.updateQuery(query, EntityWithCustomValueConverters.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // #2280
|
||||||
|
@DisplayName("should read a single String into a List property")
|
||||||
|
void shouldReadASingleStringIntoAListProperty() {
|
||||||
|
|
||||||
|
String json = "{\n" + //
|
||||||
|
" \"stringList\": \"foo\"\n" + //
|
||||||
|
"}\n"; //
|
||||||
|
Document source = Document.parse(json);
|
||||||
|
|
||||||
|
EntityWithCollections entity = mappingElasticsearchConverter.read(EntityWithCollections.class, source);
|
||||||
|
|
||||||
|
assertThat(entity.getStringList()).containsExactly("foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // #2280
|
||||||
|
@DisplayName("should read a String array into a List property")
|
||||||
|
void shouldReadAStringArrayIntoAListProperty() {
|
||||||
|
|
||||||
|
String json = "{\n" + //
|
||||||
|
" \"stringList\": [\"foo\", \"bar\"]\n" + //
|
||||||
|
"}\n"; //
|
||||||
|
Document source = Document.parse(json);
|
||||||
|
|
||||||
|
EntityWithCollections entity = mappingElasticsearchConverter.read(EntityWithCollections.class, source);
|
||||||
|
|
||||||
|
assertThat(entity.getStringList()).containsExactly("foo", "bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // #2280
|
||||||
|
@DisplayName("should read a single String into a Set property")
|
||||||
|
void shouldReadASingleStringIntoASetProperty() {
|
||||||
|
|
||||||
|
String json = "{\n" + //
|
||||||
|
" \"stringSet\": \"foo\"\n" + //
|
||||||
|
"}\n"; //
|
||||||
|
Document source = Document.parse(json);
|
||||||
|
|
||||||
|
EntityWithCollections entity = mappingElasticsearchConverter.read(EntityWithCollections.class, source);
|
||||||
|
|
||||||
|
assertThat(entity.getStringSet()).containsExactly("foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // #2280
|
||||||
|
@DisplayName("should read a String array into a Set property")
|
||||||
|
void shouldReadAStringArrayIntoASetProperty() {
|
||||||
|
|
||||||
|
String json = "{\n" + //
|
||||||
|
" \"stringSet\": [\n" + //
|
||||||
|
" \"foo\",\n" + //
|
||||||
|
" \"bar\"\n" + //
|
||||||
|
" ]\n" + //
|
||||||
|
"}\n"; //
|
||||||
|
Document source = Document.parse(json);
|
||||||
|
|
||||||
|
EntityWithCollections entity = mappingElasticsearchConverter.read(EntityWithCollections.class, source);
|
||||||
|
|
||||||
|
assertThat(entity.getStringSet()).containsExactly("foo", "bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // #2280
|
||||||
|
@DisplayName("should read a single object into a List property")
|
||||||
|
void shouldReadASingleObjectIntoAListProperty() {
|
||||||
|
|
||||||
|
String json = "{\n" + //
|
||||||
|
" \"childrenList\": {\n" + //
|
||||||
|
" \"name\": \"child\"\n" + //
|
||||||
|
" }\n" + //
|
||||||
|
"}\n"; //
|
||||||
|
Document source = Document.parse(json);
|
||||||
|
|
||||||
|
EntityWithCollections entity = mappingElasticsearchConverter.read(EntityWithCollections.class, source);
|
||||||
|
|
||||||
|
assertThat(entity.getChildrenList()).hasSize(1);
|
||||||
|
// noinspection ConstantConditions
|
||||||
|
assertThat(entity.getChildrenList().get(0).getName()).isEqualTo("child");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // #2280
|
||||||
|
@DisplayName("should read an object array into a List property")
|
||||||
|
void shouldReadAnObjectArrayIntoAListProperty() {
|
||||||
|
|
||||||
|
String json = " {\n" + //
|
||||||
|
" \"childrenList\": [\n" + //
|
||||||
|
" {\n" + //
|
||||||
|
" \"name\": \"child1\"\n" + //
|
||||||
|
" },\n" + //
|
||||||
|
" {\n" + //
|
||||||
|
" \"name\": \"child2\"\n" + //
|
||||||
|
" }\n" + //
|
||||||
|
" ]\n" + //
|
||||||
|
" }\n"; //
|
||||||
|
Document source = Document.parse(json);
|
||||||
|
|
||||||
|
EntityWithCollections entity = mappingElasticsearchConverter.read(EntityWithCollections.class, source);
|
||||||
|
|
||||||
|
assertThat(entity.getChildrenList()).hasSize(2);
|
||||||
|
// noinspection ConstantConditions
|
||||||
|
assertThat(entity.getChildrenList().get(0).getName()).isEqualTo("child1");
|
||||||
|
assertThat(entity.getChildrenList().get(1).getName()).isEqualTo("child2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // #2280
|
||||||
|
@DisplayName("should read a single object into a Set property")
|
||||||
|
void shouldReadASingleObjectIntoASetProperty() {
|
||||||
|
|
||||||
|
String json = "{\n" + //
|
||||||
|
" \"childrenSet\": {\n" + //
|
||||||
|
" \"name\": \"child\"\n" + //
|
||||||
|
" }\n" + //
|
||||||
|
"}\n"; //
|
||||||
|
Document source = Document.parse(json);
|
||||||
|
|
||||||
|
EntityWithCollections entity = mappingElasticsearchConverter.read(EntityWithCollections.class, source);
|
||||||
|
|
||||||
|
assertThat(entity.getChildrenSet()).hasSize(1);
|
||||||
|
// noinspection ConstantConditions
|
||||||
|
assertThat(entity.getChildrenSet().iterator().next().getName()).isEqualTo("child");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // #2280
|
||||||
|
@DisplayName("should read an object array into a Set property")
|
||||||
|
void shouldReadAnObjectArrayIntoASetProperty() {
|
||||||
|
|
||||||
|
String json = "{\n" + //
|
||||||
|
" \"childrenSet\": [\n" + //
|
||||||
|
" {\n" + //
|
||||||
|
" \"name\": \"child1\"\n" + //
|
||||||
|
" },\n" + //
|
||||||
|
" {\n" + //
|
||||||
|
" \"name\": \"child2\"\n" + //
|
||||||
|
" }\n" + //
|
||||||
|
" ]\n" + //
|
||||||
|
"}\n"; //
|
||||||
|
Document source = Document.parse(json);
|
||||||
|
|
||||||
|
EntityWithCollections entity = mappingElasticsearchConverter.read(EntityWithCollections.class, source);
|
||||||
|
|
||||||
|
assertThat(entity.getChildrenSet()).hasSize(2);
|
||||||
|
// noinspection ConstantConditions
|
||||||
|
List<String> names = entity.getChildrenSet().stream().map(EntityWithCollections.Child::getName)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
assertThat(names).containsExactlyInAnyOrder("child1", "child2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // #2280
|
||||||
|
@DisplayName("should read a single String into a List property immutable")
|
||||||
|
void shouldReadASingleStringIntoAListPropertyImmutable() {
|
||||||
|
|
||||||
|
String json = "{\n" + //
|
||||||
|
" \"stringList\": \"foo\"\n" + //
|
||||||
|
"}\n"; //
|
||||||
|
Document source = Document.parse(json);
|
||||||
|
|
||||||
|
ImmutableEntityWithCollections entity = mappingElasticsearchConverter.read(ImmutableEntityWithCollections.class,
|
||||||
|
source);
|
||||||
|
|
||||||
|
assertThat(entity.getStringList()).containsExactly("foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // #2280
|
||||||
|
@DisplayName("should read a String array into a List property immutable")
|
||||||
|
void shouldReadAStringArrayIntoAListPropertyImmutable() {
|
||||||
|
|
||||||
|
String json = "{\n" + //
|
||||||
|
" \"stringList\": [\n" + //
|
||||||
|
" \"foo\",\n" + //
|
||||||
|
" \"bar\"\n" + //
|
||||||
|
" ]\n" + //
|
||||||
|
"}\n"; //
|
||||||
|
Document source = Document.parse(json);
|
||||||
|
|
||||||
|
ImmutableEntityWithCollections entity = mappingElasticsearchConverter.read(ImmutableEntityWithCollections.class,
|
||||||
|
source);
|
||||||
|
|
||||||
|
assertThat(entity.getStringList()).containsExactly("foo", "bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // #2280
|
||||||
|
@DisplayName("should read a single String into a Set property immutable")
|
||||||
|
void shouldReadASingleStringIntoASetPropertyImmutable() {
|
||||||
|
|
||||||
|
String json = "{\n" + //
|
||||||
|
" \"stringSet\": \"foo\"\n" + //
|
||||||
|
"}\n"; //
|
||||||
|
Document source = Document.parse(json);
|
||||||
|
|
||||||
|
ImmutableEntityWithCollections entity = mappingElasticsearchConverter.read(ImmutableEntityWithCollections.class,
|
||||||
|
source);
|
||||||
|
|
||||||
|
assertThat(entity.getStringSet()).containsExactly("foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // #2280
|
||||||
|
@DisplayName("should read a String array into a Set property immutable")
|
||||||
|
void shouldReadAStringArrayIntoASetPropertyImmutable() {
|
||||||
|
|
||||||
|
String json = "{\n" + //
|
||||||
|
" \"stringSet\": [\n" + //
|
||||||
|
" \"foo\",\n" + //
|
||||||
|
" \"bar\"\n" + //
|
||||||
|
" ]\n" + //
|
||||||
|
"}\n"; //
|
||||||
|
Document source = Document.parse(json);
|
||||||
|
|
||||||
|
ImmutableEntityWithCollections entity = mappingElasticsearchConverter.read(ImmutableEntityWithCollections.class,
|
||||||
|
source);
|
||||||
|
|
||||||
|
assertThat(entity.getStringSet()).containsExactly("foo", "bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // #2280
|
||||||
|
@DisplayName("should read a single object into a List property immutable")
|
||||||
|
void shouldReadASingleObjectIntoAListPropertyImmutable() {
|
||||||
|
|
||||||
|
String json = "{\n" + //
|
||||||
|
" \"childrenList\": {\n" + //
|
||||||
|
" \"name\": \"child\"\n" + //
|
||||||
|
" }\n" + //
|
||||||
|
"}\n"; //
|
||||||
|
Document source = Document.parse(json);
|
||||||
|
|
||||||
|
ImmutableEntityWithCollections entity = mappingElasticsearchConverter.read(ImmutableEntityWithCollections.class,
|
||||||
|
source);
|
||||||
|
|
||||||
|
assertThat(entity.getChildrenList()).hasSize(1);
|
||||||
|
// noinspection ConstantConditions
|
||||||
|
assertThat(entity.getChildrenList().get(0).getName()).isEqualTo("child");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // #2280
|
||||||
|
@DisplayName("should read an object array into a List property immutable")
|
||||||
|
void shouldReadAnObjectArrayIntoAListPropertyImmutable() {
|
||||||
|
|
||||||
|
String json = "{\n" + //
|
||||||
|
" \"childrenList\": [\n" + //
|
||||||
|
" {\n" + //
|
||||||
|
" \"name\": \"child1\"\n" + //
|
||||||
|
" },\n" + //
|
||||||
|
" {\n" + //
|
||||||
|
" \"name\": \"child2\"\n" + //
|
||||||
|
" }\n" + //
|
||||||
|
" ]\n" + //
|
||||||
|
"}\n"; //
|
||||||
|
Document source = Document.parse(json);
|
||||||
|
|
||||||
|
ImmutableEntityWithCollections entity = mappingElasticsearchConverter.read(ImmutableEntityWithCollections.class,
|
||||||
|
source);
|
||||||
|
|
||||||
|
assertThat(entity.getChildrenList()).hasSize(2);
|
||||||
|
// noinspection ConstantConditions
|
||||||
|
assertThat(entity.getChildrenList().get(0).getName()).isEqualTo("child1");
|
||||||
|
assertThat(entity.getChildrenList().get(1).getName()).isEqualTo("child2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // #2280
|
||||||
|
@DisplayName("should read a single object into a Set property immutable")
|
||||||
|
void shouldReadASingleObjectIntoASetPropertyImmutable() {
|
||||||
|
|
||||||
|
String json = "{\n" + //
|
||||||
|
" \"childrenSet\": {\n" + //
|
||||||
|
" \"name\": \"child\"\n" + //
|
||||||
|
" }\n" + //
|
||||||
|
"}\n"; //
|
||||||
|
Document source = Document.parse(json);
|
||||||
|
|
||||||
|
ImmutableEntityWithCollections entity = mappingElasticsearchConverter.read(ImmutableEntityWithCollections.class,
|
||||||
|
source);
|
||||||
|
|
||||||
|
assertThat(entity.getChildrenSet()).hasSize(1);
|
||||||
|
// noinspection ConstantConditions
|
||||||
|
assertThat(entity.getChildrenSet().iterator().next().getName()).isEqualTo("child");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // #2280
|
||||||
|
@DisplayName("should read an object array into a Set property immutable")
|
||||||
|
void shouldReadAnObjectArrayIntoASetPropertyImmutable() {
|
||||||
|
|
||||||
|
String json = "{\n" + //
|
||||||
|
" \"childrenSet\": [\n" + //
|
||||||
|
" {\n" + //
|
||||||
|
" \"name\": \"child1\"\n" + //
|
||||||
|
" },\n" + //
|
||||||
|
" {\n" + //
|
||||||
|
" \"name\": \"child2\"\n" + //
|
||||||
|
" }\n" + //
|
||||||
|
" ]\n" + //
|
||||||
|
"}\n"; //
|
||||||
|
Document source = Document.parse(json);
|
||||||
|
|
||||||
|
ImmutableEntityWithCollections entity = mappingElasticsearchConverter.read(ImmutableEntityWithCollections.class,
|
||||||
|
source);
|
||||||
|
|
||||||
|
assertThat(entity.getChildrenSet()).hasSize(2);
|
||||||
|
// noinspection ConstantConditions
|
||||||
|
List<String> names = entity.getChildrenSet().stream().map(ImmutableEntityWithCollections.Child::getName)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
assertThat(names).containsExactlyInAnyOrder("child1", "child2");
|
||||||
|
}
|
||||||
|
|
||||||
private Map<String, Object> writeToMap(Object source) {
|
private Map<String, Object> writeToMap(Object source) {
|
||||||
|
|
||||||
Document sink = Document.create();
|
Document sink = Document.create();
|
||||||
@ -1678,34 +1992,46 @@ public class MappingElasticsearchConverterUnitTests {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o)
|
if (this == o) {
|
||||||
return true;
|
return true;
|
||||||
if (o == null || getClass() != o.getClass())
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Person person = (Person) o;
|
Person person = (Person) o;
|
||||||
|
|
||||||
if (id != null ? !id.equals(person.id) : person.id != null)
|
if (id != null ? !id.equals(person.id) : person.id != null) {
|
||||||
return false;
|
return false;
|
||||||
if (name != null ? !name.equals(person.name) : person.name != null)
|
}
|
||||||
|
if (name != null ? !name.equals(person.name) : person.name != null) {
|
||||||
return false;
|
return false;
|
||||||
if (firstName != null ? !firstName.equals(person.firstName) : person.firstName != null)
|
}
|
||||||
|
if (firstName != null ? !firstName.equals(person.firstName) : person.firstName != null) {
|
||||||
return false;
|
return false;
|
||||||
if (lastName != null ? !lastName.equals(person.lastName) : person.lastName != null)
|
}
|
||||||
|
if (lastName != null ? !lastName.equals(person.lastName) : person.lastName != null) {
|
||||||
return false;
|
return false;
|
||||||
if (birthDate != null ? !birthDate.equals(person.birthDate) : person.birthDate != null)
|
}
|
||||||
|
if (birthDate != null ? !birthDate.equals(person.birthDate) : person.birthDate != null) {
|
||||||
return false;
|
return false;
|
||||||
if (gender != person.gender)
|
}
|
||||||
|
if (gender != person.gender) {
|
||||||
return false;
|
return false;
|
||||||
if (address != null ? !address.equals(person.address) : person.address != null)
|
}
|
||||||
|
if (address != null ? !address.equals(person.address) : person.address != null) {
|
||||||
return false;
|
return false;
|
||||||
if (coWorkers != null ? !coWorkers.equals(person.coWorkers) : person.coWorkers != null)
|
}
|
||||||
|
if (coWorkers != null ? !coWorkers.equals(person.coWorkers) : person.coWorkers != null) {
|
||||||
return false;
|
return false;
|
||||||
if (inventoryList != null ? !inventoryList.equals(person.inventoryList) : person.inventoryList != null)
|
}
|
||||||
|
if (inventoryList != null ? !inventoryList.equals(person.inventoryList) : person.inventoryList != null) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
if (shippingAddresses != null ? !shippingAddresses.equals(person.shippingAddresses)
|
if (shippingAddresses != null ? !shippingAddresses.equals(person.shippingAddresses)
|
||||||
: person.shippingAddresses != null)
|
: person.shippingAddresses != null) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
return inventoryMap != null ? inventoryMap.equals(person.inventoryMap) : person.inventoryMap == null;
|
return inventoryMap != null ? inventoryMap.equals(person.inventoryMap) : person.inventoryMap == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1792,15 +2118,18 @@ public class MappingElasticsearchConverterUnitTests {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o)
|
if (this == o) {
|
||||||
return true;
|
return true;
|
||||||
if (o == null || getClass() != o.getClass())
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Gun gun = (Gun) o;
|
Gun gun = (Gun) o;
|
||||||
|
|
||||||
if (shotsPerMagazine != gun.shotsPerMagazine)
|
if (shotsPerMagazine != gun.shotsPerMagazine) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
return label.equals(gun.label);
|
return label.equals(gun.label);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1826,10 +2155,12 @@ public class MappingElasticsearchConverterUnitTests {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o)
|
if (this == o) {
|
||||||
return true;
|
return true;
|
||||||
if (!(o instanceof Grenade))
|
}
|
||||||
|
if (!(o instanceof Grenade)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Grenade grenade = (Grenade) o;
|
Grenade grenade = (Grenade) o;
|
||||||
|
|
||||||
@ -1862,17 +2193,21 @@ public class MappingElasticsearchConverterUnitTests {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o)
|
if (this == o) {
|
||||||
return true;
|
return true;
|
||||||
if (!(o instanceof Rifle))
|
}
|
||||||
|
if (!(o instanceof Rifle)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Rifle rifle = (Rifle) o;
|
Rifle rifle = (Rifle) o;
|
||||||
|
|
||||||
if (Double.compare(rifle.weight, weight) != 0)
|
if (Double.compare(rifle.weight, weight) != 0) {
|
||||||
return false;
|
return false;
|
||||||
if (maxShotsPerMagazine != rifle.maxShotsPerMagazine)
|
}
|
||||||
|
if (maxShotsPerMagazine != rifle.maxShotsPerMagazine) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
return label.equals(rifle.label);
|
return label.equals(rifle.label);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1903,10 +2238,12 @@ public class MappingElasticsearchConverterUnitTests {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o)
|
if (this == o) {
|
||||||
return true;
|
return true;
|
||||||
if (!(o instanceof ShotGun))
|
}
|
||||||
|
if (!(o instanceof ShotGun)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
ShotGun shotGun = (ShotGun) o;
|
ShotGun shotGun = (ShotGun) o;
|
||||||
|
|
||||||
@ -1953,17 +2290,21 @@ public class MappingElasticsearchConverterUnitTests {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o)
|
if (this == o) {
|
||||||
return true;
|
return true;
|
||||||
if (!(o instanceof Address))
|
}
|
||||||
|
if (!(o instanceof Address)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Address address = (Address) o;
|
Address address = (Address) o;
|
||||||
|
|
||||||
if (location != null ? !location.equals(address.location) : address.location != null)
|
if (location != null ? !location.equals(address.location) : address.location != null) {
|
||||||
return false;
|
return false;
|
||||||
if (street != null ? !street.equals(address.street) : address.street != null)
|
}
|
||||||
|
if (street != null ? !street.equals(address.street) : address.street != null) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
return city != null ? city.equals(address.city) : address.city == null;
|
return city != null ? city.equals(address.city) : address.city == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1990,10 +2331,12 @@ public class MappingElasticsearchConverterUnitTests {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o)
|
if (this == o) {
|
||||||
return true;
|
return true;
|
||||||
if (!(o instanceof Place))
|
}
|
||||||
|
if (!(o instanceof Place)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Place place = (Place) o;
|
Place place = (Place) o;
|
||||||
|
|
||||||
@ -2462,6 +2805,128 @@ public class MappingElasticsearchConverterUnitTests {
|
|||||||
return reverse(value);
|
return reverse(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class EntityWithCollections {
|
||||||
|
@Field(type = FieldType.Keyword)
|
||||||
|
@Nullable private List<String> stringList;
|
||||||
|
|
||||||
|
@Field(type = FieldType.Keyword)
|
||||||
|
@Nullable private Set<String> stringSet;
|
||||||
|
|
||||||
|
@Field(type = FieldType.Object)
|
||||||
|
@Nullable private List<Child> childrenList;
|
||||||
|
|
||||||
|
@Field(type = FieldType.Object)
|
||||||
|
@Nullable private Set<Child> childrenSet;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public List<String> getStringList() {
|
||||||
|
return stringList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStringList(@Nullable List<String> stringList) {
|
||||||
|
this.stringList = stringList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Set<String> getStringSet() {
|
||||||
|
return stringSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStringSet(@Nullable Set<String> stringSet) {
|
||||||
|
this.stringSet = stringSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public List<Child> getChildrenList() {
|
||||||
|
return childrenList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChildrenList(@Nullable List<Child> childrenList) {
|
||||||
|
this.childrenList = childrenList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Set<Child> getChildrenSet() {
|
||||||
|
return childrenSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChildrenSet(@Nullable Set<Child> childrenSet) {
|
||||||
|
this.childrenSet = childrenSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Child {
|
||||||
|
|
||||||
|
@Field(type = FieldType.Keyword)
|
||||||
|
@Nullable private String name;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ImmutableEntityWithCollections {
|
||||||
|
@Field(type = FieldType.Keyword)
|
||||||
|
@Nullable private List<String> stringList;
|
||||||
|
|
||||||
|
@Field(type = FieldType.Keyword)
|
||||||
|
@Nullable private Set<String> stringSet;
|
||||||
|
|
||||||
|
@Field(type = FieldType.Object)
|
||||||
|
@Nullable private List<Child> childrenList;
|
||||||
|
|
||||||
|
@Field(type = FieldType.Object)
|
||||||
|
@Nullable private Set<Child> childrenSet;
|
||||||
|
|
||||||
|
public ImmutableEntityWithCollections(@Nullable List<String> stringList, @Nullable Set<String> stringSet,
|
||||||
|
@Nullable List<Child> childrenList, @Nullable Set<Child> childrenSet) {
|
||||||
|
this.stringList = stringList;
|
||||||
|
this.stringSet = stringSet;
|
||||||
|
this.childrenList = childrenList;
|
||||||
|
this.childrenSet = childrenSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public List<String> getStringList() {
|
||||||
|
return stringList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Set<String> getStringSet() {
|
||||||
|
return stringSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public List<Child> getChildrenList() {
|
||||||
|
return childrenList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Set<Child> getChildrenSet() {
|
||||||
|
return childrenSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Child {
|
||||||
|
|
||||||
|
@Field(type = FieldType.Keyword)
|
||||||
|
@Nullable private String name;
|
||||||
|
|
||||||
|
public Child(@Nullable String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
private static String reverse(Object o) {
|
private static String reverse(Object o) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user