Skip to content

Commit

Permalink
Merge branch 'main' into FMWK-263-update-documentation
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/main/java/org/springframework/data/aerospike/convert/MappingAerospikeWriteConverter.java
#	src/main/java/org/springframework/data/aerospike/core/AerospikeOperations.java
#	src/main/java/org/springframework/data/aerospike/core/ReactiveAerospikeOperations.java
#	src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindByIdTests.java
#	src/test/java/org/springframework/data/aerospike/repository/PersonRepositoryQueryTests.java
  • Loading branch information
agrgr committed Dec 6, 2023
2 parents 57ad6cd + 41ac7a8 commit 71d8040
Show file tree
Hide file tree
Showing 55 changed files with 1,699 additions and 492 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ public IndexesCacheHolder indexCache() {
public MappingAerospikeConverter mappingAerospikeConverter(AerospikeMappingContext aerospikeMappingContext,
AerospikeTypeAliasAccessor aerospikeTypeAliasAccessor,
AerospikeCustomConversions customConversions) {
return new MappingAerospikeConverter(aerospikeMappingContext, customConversions, aerospikeTypeAliasAccessor);
return new MappingAerospikeConverter(aerospikeMappingContext, customConversions, aerospikeTypeAliasAccessor,
aerospikeDataSettings());
}

@Bean(name = "aerospikeTypeAliasAccessor")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ public class AerospikeDataSettings {
@Builder.Default
// Limit amount of results returned by server. Non-positive value means no limit
long queryMaxRecords = 10_000L;
// Define how @Id fields (primary keys) and Map keys are stored: false - always as String,
// true - preserve original type if supported
@Builder.Default
boolean keepOriginalKeyTypes = false;

/*
* (non-Javadoc)
Expand All @@ -46,5 +50,6 @@ public class AerospikeDataSettings {
* it will satisfy javadoc and won't interfere with the @Builder annotation's normal behaviour.
*/
public static class AerospikeDataSettingsBuilder {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.springframework.data.aerospike.convert;

import org.springframework.core.convert.ConversionService;
import org.springframework.data.aerospike.config.AerospikeDataSettings;
import org.springframework.data.convert.EntityConverter;

/**
Expand All @@ -31,4 +32,11 @@ public interface AerospikeConverter extends AerospikeReader<Object>, AerospikeWr
* @return the underlying {@link ConversionService} used by the converter
*/
ConversionService getConversionService();

/**
* Access Aerospike-specific data settings.
*
* @return the underlying {@link AerospikeDataSettings} used by the converter
*/
AerospikeDataSettings getAerospikeDataSettings();
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@
*/
package org.springframework.data.aerospike.convert;

import lombok.Getter;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.aerospike.config.AerospikeDataSettings;
import org.springframework.data.aerospike.mapping.AerospikeMappingContext;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.convert.DefaultTypeMapper;
Expand All @@ -39,24 +40,30 @@
public class MappingAerospikeConverter implements InitializingBean, AerospikeConverter {

private final CustomConversions conversions;
@Getter
private final GenericConversionService conversionService;
@Getter
private final AerospikeDataSettings aerospikeDataSettings;
private final MappingAerospikeReadConverter readConverter;
private final MappingAerospikeWriteConverter writeConverter;

/**
* Creates a new {@link MappingAerospikeConverter}.
*/
public MappingAerospikeConverter(AerospikeMappingContext mappingContext, CustomConversions conversions,
AerospikeTypeAliasAccessor aerospikeTypeAliasAccessor) {
AerospikeTypeAliasAccessor aerospikeTypeAliasAccessor,
AerospikeDataSettings aerospikeDataSettings) {
this.conversions = conversions;
this.conversionService = new DefaultConversionService();
this.aerospikeDataSettings = aerospikeDataSettings;

EntityInstantiators entityInstantiators = new EntityInstantiators();
TypeMapper<Map<String, Object>> typeMapper = new DefaultTypeMapper<>(aerospikeTypeAliasAccessor,
mappingContext, List.of(new SimpleTypeInformationMapper()));

this.writeConverter =
new MappingAerospikeWriteConverter(typeMapper, mappingContext, conversions, conversionService);
new MappingAerospikeWriteConverter(typeMapper, mappingContext, conversions, conversionService,
aerospikeDataSettings);
this.readConverter = new MappingAerospikeReadConverter(entityInstantiators, aerospikeTypeAliasAccessor,
typeMapper, mappingContext, conversions, conversionService);
}
Expand All @@ -66,11 +73,6 @@ public void afterPropertiesSet() {
conversions.registerConvertersIn(conversionService);
}

@Override
public ConversionService getConversionService() {
return conversionService;
}

@Override
public <R> R read(Class<R> type, final AerospikeReadData data) {
return readConverter.read(type, data);
Expand All @@ -84,4 +86,8 @@ public void write(Object source, AerospikeWriteData sink) {
public Object toWritableValue(Object source, TypeInformation<?> type) {
return writeConverter.getValueToWrite(source, type);
}

public CustomConversions getCustomConversions() {
return this.conversions;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@

import com.aerospike.client.AerospikeException;
import com.aerospike.client.Key;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.aerospike.config.AerospikeDataSettings;
import org.springframework.data.aerospike.mapping.AerospikeMappingContext;
import org.springframework.data.aerospike.mapping.AerospikePersistentEntity;
import org.springframework.data.aerospike.mapping.AerospikePersistentProperty;
Expand All @@ -31,6 +33,7 @@
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
Expand All @@ -43,20 +46,24 @@
import static com.aerospike.client.ResultCode.OP_NOT_APPLICABLE;
import static org.springframework.data.aerospike.utility.TimeUtils.unixTimeToOffsetInSeconds;

@Slf4j
public class MappingAerospikeWriteConverter implements EntityWriter<Object, AerospikeWriteData> {

private final TypeMapper<Map<String, Object>> typeMapper;
private final AerospikeMappingContext mappingContext;
private final CustomConversions conversions;
private final GenericConversionService conversionService;
private final AerospikeDataSettings aerospikeDataSettings;

public MappingAerospikeWriteConverter(TypeMapper<Map<String, Object>> typeMapper,
AerospikeMappingContext mappingContext, CustomConversions conversions,
GenericConversionService conversionService) {
GenericConversionService conversionService,
AerospikeDataSettings aerospikeDataSettings) {
this.typeMapper = typeMapper;
this.mappingContext = mappingContext;
this.conversions = conversions;
this.conversionService = conversionService;
this.aerospikeDataSettings = aerospikeDataSettings;
}

private static Collection<?> asCollection(final Object source) {
Expand Down Expand Up @@ -114,15 +121,24 @@ public Optional<Key> getNewKey(AerospikeWriteData data,
|| key.setName == null || key.namespace == null) {
AerospikePersistentProperty idProperty = entity.getIdProperty();
if (idProperty != null) {
String id = accessor.getProperty(idProperty, String.class); // currently id is read as a String
Assert.notNull(id, "Id must not be null!");
String setName;
if (data.getSetName() != null) {
setName = data.getSetName();
} else {
setName = entity.getSetName();
String setName = Optional.ofNullable(data.getSetName()).orElse(entity.getSetName());

// Store record key as it is (if Aerospike supports it natively and configured)
if (aerospikeDataSettings.isKeepOriginalKeyTypes()
&& isValidAerospikeRecordKeyType(idProperty.getType())) {
log.debug("Attempt to construct record key with original key type");
Object nativeTypeId = accessor.getProperty(idProperty, idProperty.getType());
Assert.notNull(nativeTypeId, "Id must not be null!");
Key aerospikeRecordKey = constructAerospikeRecordKey(data.getNamespace(), setName, nativeTypeId);
if (aerospikeRecordKey != null) {
return Optional.of(aerospikeRecordKey);
}
}
return Optional.of(new Key(data.getNamespace(), setName, id));
// Store record key as a String (Used for unsupported Aerospike key types and older versions)
String stringId = accessor.getProperty(idProperty, String.class);
Assert.notNull(stringId, "Id must not be null!");
log.debug("Attempt to construct record key as String");
return Optional.of(new Key(data.getNamespace(), setName, stringId));
} else {
// id is mandatory
throw new AerospikeException(OP_NOT_APPLICABLE, "Id has not been provided");
Expand Down Expand Up @@ -214,7 +230,7 @@ protected List<Object> convertCollection(final Collection<?> source, final TypeI
return source.stream().map(element -> getValueToWrite(element, componentType)).collect(Collectors.toList());
}

protected Map<String, Object> convertMap(final Map<Object, Object> source, final TypeInformation<?> type) {
protected Map<Object, Object> convertMap(final Map<Object, Object> source, final TypeInformation<?> type) {
Assert.notNull(source, "Given map must not be null!");
Assert.notNull(type, "Given type must not be null!");

Expand All @@ -229,14 +245,21 @@ protected Map<String, Object> convertMap(final Map<Object, Object> source, final
throw new MappingException("Cannot use a complex object as a map key");
}

String simpleKey;
if (conversionService.canConvert(key.getClass(), String.class)) {
simpleKey = conversionService.convert(key, String.class);
Object simpleKey;

if (aerospikeDataSettings.isKeepOriginalKeyTypes() &&
isValidAerospikeMapKeyType(key.getClass())) {
simpleKey = key;
} else {
simpleKey = key.toString();
if (conversionService.canConvert(key.getClass(), String.class)) {
simpleKey = conversionService.convert(key, String.class);
} else {
simpleKey = key.toString();
}
}

Object convertedValue = getValueToWrite(value, type.getMapValueType());
if (simpleKey instanceof byte[]) simpleKey = ByteBuffer.wrap((byte[]) simpleKey);
m.put(simpleKey, convertedValue);
}, TreeMap::putAll);
}
Expand Down Expand Up @@ -287,4 +310,37 @@ private int getExpirationFromProperty(ConvertingPropertyAccessor<?> accessor,

return expirationInSeconds;
}

private boolean isValidAerospikeRecordKeyType(Class<?> type) {
return type == Short.TYPE || type == Integer.TYPE || type == Long.TYPE || type == Byte.TYPE ||
type == Character.TYPE ||
type == Short.class || type == Integer.class || type == Long.class || type == Byte.class ||
type == Character.class || type == String.class ||
type == byte[].class;
}

private Key constructAerospikeRecordKey(String namespace, String set, Object userKey) {
// At this point there are no primitives types for userKey, only wrappers
if (userKey.getClass() == String.class) {
return new Key(namespace, set, (String) userKey);
} else if (userKey.getClass() == Short.class) {
return new Key(namespace, set, (short) userKey);
} else if (userKey.getClass() == Integer.class) {
return new Key(namespace, set, (int) userKey);
} else if (userKey.getClass() == Long.class) {
return new Key(namespace, set, (long) userKey);
} else if (userKey.getClass() == Character.class) {
return new Key(namespace, set, (char) userKey);
} else if (userKey.getClass() == Byte.class) {
return new Key(namespace, set, (byte) userKey);
} else if (userKey.getClass() == byte[].class) {
return new Key(namespace, set, (byte[]) userKey);
}
// Could not construct a key of native supported Aerospike record key type
return null;
}

private boolean isValidAerospikeMapKeyType(Class<?> type) {
return isValidAerospikeRecordKeyType(type) || type == Double.TYPE || type == Double.class;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.aerospike.client.query.IndexType;
import com.aerospike.client.query.ResultSet;
import org.springframework.data.aerospike.config.AerospikeDataSettings;
import org.springframework.data.aerospike.convert.MappingAerospikeConverter;
import org.springframework.data.aerospike.core.model.GroupedEntities;
import org.springframework.data.aerospike.core.model.GroupedKeys;
import org.springframework.data.aerospike.repository.query.Query;
Expand Down Expand Up @@ -70,6 +71,11 @@ public interface AerospikeOperations {
*/
MappingContext<?, ?> getMappingContext();

/**
* @return converter in use.
*/
MappingAerospikeConverter getAerospikeConverter();

/**
* @return Aerospike client in use.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ public <T> String getSetName(T document) {
return this.mappingContext;
}

public MappingAerospikeConverter getAerospikeConverter() {
return this.converter;
}

public String getNamespace() {
return namespace;
}
Expand Down Expand Up @@ -278,16 +282,28 @@ WritePolicy ignoreGenerationDeletePolicy() {
}

Key getKey(Object id, AerospikePersistentEntity<?> entity) {
Assert.notNull(id, "Id must not be null!");
String userKey = convertIfNecessary(id, String.class);
return new Key(this.namespace, entity.getSetName(), userKey);
return getKey(id, entity.getSetName());
}

Key getKey(Object id, String setName) {
Assert.notNull(id, "Id must not be null!");
Assert.notNull(setName, "Set name must not be null!");
String userKey = convertIfNecessary(id, String.class);
return new Key(this.namespace, setName, userKey);
Key key;
// choosing whether tp preserve id type based on the configuration
if (converter.getAerospikeDataSettings().isKeepOriginalKeyTypes()) {
if (id instanceof Byte || id instanceof Short || id instanceof Integer || id instanceof Long) {
key = new Key(this.namespace, setName, convertIfNecessary(((Number) id).longValue(), Long.class));
} else if (id instanceof Character) {
key = new Key(this.namespace, setName, convertIfNecessary(id, Character.class));
} else if (id instanceof byte[]) {
key = new Key(this.namespace, setName, convertIfNecessary(id, byte[].class));
} else {
key = new Key(this.namespace, setName, convertIfNecessary(id, String.class));
}
return key;
} else {
return new Key(this.namespace, setName, convertIfNecessary(id, String.class));
}
}

GroupedEntities toGroupedEntities(EntitiesKeys entitiesKeys, Record[] records) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
import org.springframework.data.aerospike.repository.query.Query;
import org.springframework.data.domain.Sort;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
Expand Down Expand Up @@ -95,8 +95,9 @@ static void verifyUnsortedWithOffset(Sort sort, long offset) {
static Predicate<KeyRecord> getDistinctPredicate(Query query) {
Predicate<KeyRecord> distinctPredicate;
if (query != null && query.isDistinct()) {
String dotPathString = query.getCriteriaObject().getDotPath();
if (StringUtils.hasLength(dotPathString)) {
List<String> dotPathList = query.getCriteriaObject().getDotPath();
if (dotPathList != null && dotPathList.size() > 0 && dotPathList.get(0) != null) {
String dotPathString = String.join(",", query.getCriteriaObject().getDotPath());
throw new UnsupportedOperationException("DISTINCT queries are currently supported only for the first " +
"level objects, got a query for " + dotPathString);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.aerospike.client.query.IndexType;
import com.aerospike.client.reactor.IAerospikeReactorClient;
import org.springframework.data.aerospike.config.AerospikeDataSettings;
import org.springframework.data.aerospike.convert.MappingAerospikeConverter;
import org.springframework.data.aerospike.core.model.GroupedEntities;
import org.springframework.data.aerospike.core.model.GroupedKeys;
import org.springframework.data.aerospike.repository.query.Query;
Expand All @@ -47,6 +48,11 @@ public interface ReactiveAerospikeOperations {
*/
MappingContext<?, ?> getMappingContext();

/**
* @return converter in use.
*/
MappingAerospikeConverter getAerospikeConverter();

/**
* @return Aerospike reactive client in use.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,13 @@ public static List<Object> getIdValue(Qualifier qualifier) {
private static List<Object> idObjectToList(Object ids) {
List<Object> result;
Assert.notNull(ids, "Ids must not be null");

if (ids.getClass().isArray()) {
result = Arrays.stream(((Object[]) ids)).toList();
if (ids instanceof byte[]) {
result = List.of(ids);
} else {
result = Arrays.stream(((Object[]) ids)).toList();
}
} else if (ids instanceof Collection<?>) {
result = new ArrayList<>((Collection<?>) ids);
} else if (ids instanceof Iterable<?>) {
Expand Down
Loading

0 comments on commit 71d8040

Please sign in to comment.