microsoft / spring-data-cosmosdb

Access data with Azure Cosmos DB
MIT License
93 stars 68 forks source link

ObjectMapper is not always take into account #429

Closed alain-marcel closed 4 years ago

alain-marcel commented 4 years ago

Hi

Used versions : com.microsoft.azure:spring-data-cosmosdb:2.1.7 and 3.0.0.M1)

If we declare an ObjectMapper, then it is not used in following case : File: com.microsoft.azure.spring.data.cosmosdb.core.DocumentDbTemplate Method: find(@NonNull DocumentQuery query, @NonNull Class domainClass, String collectionName) Line 279 : .map(cosmosItemProperties -> cosmosItemProperties.toObject(domainClass))

In fact, the implementation should use mappingDocumentDbConverter attribute to deserialize object.

Workaround

The key code is to override find method :

@Override
public <T> List<T> find(@NonNull DocumentQuery query, @NonNull Class<T> domainClass, String collectionName) {
    Assert.notNull(query, "DocumentQuery should not be null.");
    Assert.notNull(domainClass, "domainClass should not be null.");
    Assert.hasText(collectionName, "collection should not be null, empty or only whitespaces");

    try {
        return findDocuments(query, domainClass, collectionName)
                .stream()
                // START BUGFIX
                // OLD CODE: .map(cosmosItemProperties -> cosmosItemProperties.toObject(domainClass))
                .map((CosmosItemProperties cosmosItemProperties) -> toDomainObject(domainClass, cosmosItemProperties))
                // END BUGFIX
                .collect(Collectors.toList());
    } catch (Exception e) {

        throw new DocumentDBAccessException("Failed to execute find operation from " + collectionName, e);
    }
}

private <T> T toDomainObject(@NonNull Class<T> domainClass, CosmosItemProperties cosmosItemProperties) {
    return mappingDocumentDbConverter.read(domainClass, cosmosItemProperties);
}

Action

Could you please integrate this in next version ? Thanks.

Full MyDocumentDbTemplate.java

package org.me;

import com.azure.data.cosmos.CosmosClient; import com.azure.data.cosmos.CosmosItemProperties; import com.azure.data.cosmos.FeedOptions; import com.azure.data.cosmos.SqlQuerySpec; import com.microsoft.azure.spring.data.cosmosdb.CosmosDbFactory; import com.microsoft.azure.spring.data.cosmosdb.core.DocumentDbTemplate; import com.microsoft.azure.spring.data.cosmosdb.core.convert.MappingDocumentDbConverter; import com.microsoft.azure.spring.data.cosmosdb.core.generator.FindQuerySpecGenerator; import com.microsoft.azure.spring.data.cosmosdb.core.query.DocumentQuery; import com.microsoft.azure.spring.data.cosmosdb.exception.DocumentDBAccessException; import com.microsoft.azure.spring.data.cosmosdb.repository.support.DocumentDbEntityInformation; import org.springframework.lang.NonNull; import org.springframework.util.Assert; import reactor.core.publisher.Flux;

import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors;

public class MyDocumentDbTemplate extends DocumentDbTemplate { private final MappingDocumentDbConverter mappingDocumentDbConverter; private final String databaseName; private final CosmosClient cosmosClient;

public MyDocumentDbTemplate(CosmosDbFactory cosmosDbFactory, MappingDocumentDbConverter mappingDocumentDbConverter, String databaseName) {
    super(cosmosDbFactory, mappingDocumentDbConverter, databaseName);

    this.databaseName = databaseName;
    this.cosmosClient = cosmosDbFactory.getCosmosClient();
    this.mappingDocumentDbConverter = mappingDocumentDbConverter;
}

/**
 * Fix: overridden because deserializes the JSON string without using the configured {@link com.fasterxml.jackson.databind.ObjectMapper}.
 *
 * @implNote Copy / paste from {@link DocumentDbTemplate#find(DocumentQuery, Class, String)}, version 2.1.7
 */
@Override
public <T> List<T> find(@NonNull DocumentQuery query, @NonNull Class<T> domainClass, String collectionName) {
    Assert.notNull(query, "DocumentQuery should not be null.");
    Assert.notNull(domainClass, "domainClass should not be null.");
    Assert.hasText(collectionName, "collection should not be null, empty or only whitespaces");

    try {
        return findDocuments(query, domainClass, collectionName)
                .stream()
                // START BUGFIX
                // OLD CODE: .map(cosmosItemProperties -> cosmosItemProperties.toObject(domainClass))
                .map((CosmosItemProperties cosmosItemProperties) -> toDomainObject(domainClass, cosmosItemProperties))
                // END BUGFIX
                .collect(Collectors.toList());
    } catch (Exception e) {
        throw new DocumentDBAccessException("Failed to execute find operation from " + collectionName, e);
    }
}

private <T> T toDomainObject(@NonNull Class<T> domainClass, CosmosItemProperties cosmosItemProperties) {
    return mappingDocumentDbConverter.read(domainClass, cosmosItemProperties);
}

/**
 * @implNote Copy / paste from {@link DocumentDbTemplate#findDocuments(DocumentQuery, Class, String)}, version 2.1.7
 * because the method is private in the super class
 */
private List<CosmosItemProperties> findDocuments(@NonNull DocumentQuery query,
                                                 @NonNull Class<?> domainClass,
                                                 @NonNull String containerName) {
    final SqlQuerySpec sqlQuerySpec = new FindQuerySpecGenerator().generateCosmos(query);
    final boolean isCrossPartitionQuery =
            query.isCrossPartitionQuery(getPartitionKeyNames(domainClass));
    final FeedOptions feedOptions = new FeedOptions();
    feedOptions.enableCrossPartitionQuery(isCrossPartitionQuery);
    return cosmosClient
            .getDatabase(this.databaseName)
            .getContainer(containerName)
            .queryItems(sqlQuerySpec, feedOptions)
            .flatMap(cosmosItemFeedResponse -> Flux.fromIterable(cosmosItemFeedResponse.results()))
            .collectList()
            .block();
}

/**
 * @implNote Copy / paste from {@link DocumentDbTemplate#getPartitionKeyNames(Class)}, version 2.1.7
 * because the method is private in the super class
 */
private List<String> getPartitionKeyNames(Class<?> domainClass) {
    final DocumentDbEntityInformation entityInfo = new DocumentDbEntityInformation(domainClass);

    if (entityInfo.getPartitionKeyFieldName() == null) {
        return new ArrayList<>();
    }

    return Collections.singletonList(entityInfo.getPartitionKeyFieldName());
}

}

kushagraThapar commented 4 years ago

@alain-marcel Thank you for noticing and providing the solution. I have integrated this fix in the PR: https://github.com/microsoft/spring-data-cosmosdb/pull/430

This will get released with 2.1.8 sometime next week.