Skip to content

Commit 1b05386

Browse files
authored
make FirestoreClassMapper a Bean (spring-attic#2060)
* make FirestoreClassMapper a bean
1 parent 46887d4 commit 1b05386

File tree

10 files changed

+130
-43
lines changed

10 files changed

+130
-43
lines changed

spring-cloud-gcp-autoconfigure/src/main/java/org/springframework/cloud/gcp/autoconfigure/firestore/GcpFirestoreAutoConfiguration.java

+12-2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
import org.springframework.cloud.gcp.core.GcpProjectIdProvider;
3939
import org.springframework.cloud.gcp.core.UserAgentHeaderProvider;
4040
import org.springframework.cloud.gcp.data.firestore.FirestoreTemplate;
41+
import org.springframework.cloud.gcp.data.firestore.mapping.FirestoreClassMapper;
42+
import org.springframework.cloud.gcp.data.firestore.mapping.FirestoreDefaultClassMapper;
4143
import org.springframework.cloud.gcp.data.firestore.mapping.FirestoreMappingContext;
4244
import org.springframework.cloud.gcp.data.firestore.transaction.ReactiveFirestoreTransactionManager;
4345
import org.springframework.context.annotation.Bean;
@@ -128,8 +130,16 @@ public FirestoreMappingContext firestoreMappingContext() {
128130

129131
@Bean
130132
@ConditionalOnMissingBean
131-
public FirestoreTemplate firestoreTemplate(FirestoreGrpc.FirestoreStub firestoreStub) {
132-
return new FirestoreTemplate(firestoreStub, GcpFirestoreAutoConfiguration.this.firestoreRootPath);
133+
public FirestoreClassMapper getClassMapper() {
134+
return new FirestoreDefaultClassMapper();
135+
}
136+
137+
@Bean
138+
@ConditionalOnMissingBean
139+
public FirestoreTemplate firestoreTemplate(FirestoreGrpc.FirestoreStub firestoreStub,
140+
FirestoreClassMapper classMapper) {
141+
return new FirestoreTemplate(firestoreStub, GcpFirestoreAutoConfiguration.this.firestoreRootPath,
142+
classMapper);
133143
}
134144

135145
@Bean

spring-cloud-gcp-data-firestore/src/main/java/org/springframework/cloud/gcp/data/firestore/FirestoreTemplate.java

+14-12
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import java.time.Duration;
2020
import java.util.List;
21-
import java.util.Map;
2221
import java.util.Optional;
2322
import java.util.function.Consumer;
2423

@@ -29,7 +28,6 @@
2928
import com.google.firestore.v1.RunQueryRequest;
3029
import com.google.firestore.v1.RunQueryResponse;
3130
import com.google.firestore.v1.StructuredQuery;
32-
import com.google.firestore.v1.Value;
3331
import com.google.firestore.v1.Write;
3432
import com.google.firestore.v1.WriteRequest;
3533
import com.google.firestore.v1.WriteResponse;
@@ -39,10 +37,10 @@
3937
import reactor.core.publisher.Mono;
4038
import reactor.util.context.Context;
4139

40+
import org.springframework.cloud.gcp.data.firestore.mapping.FirestoreClassMapper;
4241
import org.springframework.cloud.gcp.data.firestore.mapping.FirestoreMappingContext;
4342
import org.springframework.cloud.gcp.data.firestore.mapping.FirestorePersistentEntity;
4443
import org.springframework.cloud.gcp.data.firestore.mapping.FirestorePersistentProperty;
45-
import org.springframework.cloud.gcp.data.firestore.mapping.PublicClassMapper;
4644
import org.springframework.cloud.gcp.data.firestore.transaction.ReactiveFirestoreResourceHolder;
4745
import org.springframework.cloud.gcp.data.firestore.util.ObservableReactiveUtil;
4846
import org.springframework.cloud.gcp.data.firestore.util.Util;
@@ -54,9 +52,11 @@
5452
*
5553
* @author Dmitry Solomakha
5654
* @author Chengyuan Zhao
55+
* @author Mike Eltsufin
5756
* @since 1.2
5857
*/
5958
public class FirestoreTemplate implements FirestoreReactiveOperations {
59+
private FirestoreClassMapper classMapper;
6060

6161
private static final int FIRESTORE_WRITE_MAX_SIZE = 500;
6262

@@ -87,12 +87,13 @@ public class FirestoreTemplate implements FirestoreReactiveOperations {
8787
* @param firestore Firestore gRPC stub
8888
* @param parent the parent resource. For example:
8989
* projects/{project_id}/databases/{database_id}/documents or
90-
* projects/{project_id}/databases/{database_id}/documents/chatrooms/{chatroom_id}
90+
* @param classMapper a {@link FirestoreClassMapper} used for conversion
9191
*/
92-
public FirestoreTemplate(FirestoreStub firestore, String parent) {
92+
public FirestoreTemplate(FirestoreStub firestore, String parent, FirestoreClassMapper classMapper) {
9393
this.firestore = firestore;
9494
this.parent = parent;
9595
this.databasePath = Util.extractDatabasePath(parent);
96+
this.classMapper = classMapper;
9697
}
9798

9899
/**
@@ -145,7 +146,7 @@ public <T> Flux<T> findAllById(Publisher<String> idPublisher, Class<T> entityCla
145146
return Flux.from(idPublisher)
146147
.flatMap(id -> getDocument(id, entityClass, null))
147148
.onErrorMap(throwable -> new FirestoreDataException("Error while reading entries by id", throwable))
148-
.map(document -> PublicClassMapper.convertToCustomClass(document, entityClass));
149+
.map(document -> getClassMapper().documentToEntity(document, entityClass));
149150
}
150151

151152
@Override
@@ -181,7 +182,7 @@ public <T> Flux<T> saveAll(Publisher<T> instances) {
181182
public <T> Flux<T> findAll(Class<T> clazz) {
182183
return Flux.defer(() ->
183184
findAllDocuments(clazz)
184-
.map(document -> PublicClassMapper.convertToCustomClass(document, clazz)));
185+
.map(document -> getClassMapper().documentToEntity(document, clazz)));
185186
}
186187

187188
@Override
@@ -236,7 +237,7 @@ public <T> Mono<Void> deleteById(Publisher<String> idPublisher, Class entityClas
236237
public <T> Flux<T> execute(StructuredQuery.Builder builder, Class<T> entityType) {
237238
return Flux.defer(() ->
238239
findAllDocuments(entityType, null, builder)
239-
.map(document -> PublicClassMapper.convertToCustomClass(document, entityType)));
240+
.map(document -> getClassMapper().documentToEntity(document, entityType)));
240241
}
241242

242243
public FirestoreMappingContext getMappingContext() {
@@ -357,11 +358,9 @@ private <T> WriteRequest buildWriteRequest(List<T> entityList, WriteResponse wri
357358

358359
private <T> Write createUpdateWrite(T entity) {
359360
String documentResourceName = buildResourceName(entity);
360-
Map<String, Value> valuesMap = PublicClassMapper.convertToFirestoreTypes(entity);
361+
Document document = getClassMapper().entityToDocument(entity, documentResourceName);
361362
return Write.newBuilder()
362-
.setUpdate(Document.newBuilder()
363-
.putAllFields(valuesMap)
364-
.setName(documentResourceName))
363+
.setUpdate(document)
365364
.build();
366365
}
367366

@@ -385,4 +384,7 @@ private String getIdValue(Object entity, FirestorePersistentEntity persistentEnt
385384
return idVal.toString();
386385
}
387386

387+
public FirestoreClassMapper getClassMapper() {
388+
return this.classMapper;
389+
}
388390
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2019-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.gcp.data.firestore.mapping;
18+
19+
import com.google.firestore.v1.Document;
20+
import com.google.firestore.v1.Value;
21+
22+
/**
23+
* An interface used for object mapping for Cloud Firestore.
24+
*
25+
* @author Dmitry Solomakha
26+
* @author Mike Eltsufin
27+
* @since 1.3
28+
*/
29+
public interface FirestoreClassMapper {
30+
/**
31+
* Converts an entity to a Firestore type.
32+
*
33+
* @param <T> the type of the object to convert
34+
* @param sourceValue the object to convert
35+
* @return value that can be used to bind to a Firestore query
36+
*/
37+
<T> Value toFirestoreValue(T sourceValue);
38+
39+
/**
40+
* Converts an entity to a Firestore document.
41+
*
42+
* @param <T> the type of the object to convert
43+
* @param entity the object to convert
44+
* @param documentResourceName the fully-qualified identifier of the document
45+
* @return a {@link Document} that can be stored in Firestore
46+
*/
47+
<T> Document entityToDocument(T entity, String documentResourceName);
48+
49+
/**
50+
* Converts a Firestore document to an entity.
51+
*
52+
* @param <T> the type of the target object
53+
* @param document the {@link Document} to convert
54+
* @param clazz the type of the target entity
55+
* @return the entity that the Firestore document was converted to
56+
*/
57+
<T> T documentToEntity(Document document, Class<T> clazz);
58+
}

spring-cloud-gcp-data-firestore/src/main/java/org/springframework/cloud/gcp/data/firestore/mapping/PublicClassMapper.java spring-cloud-gcp-data-firestore/src/main/java/org/springframework/cloud/gcp/data/firestore/mapping/FirestoreDefaultClassMapper.java

+13-9
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,13 @@
2929

3030
/**
3131
*
32-
* Temporary class to expose package-private methods, will be removed in the future.
32+
* Uses Firestore client library to provide object mapping functionality.
3333
*
3434
* @author Dmitry Solomakha
35-
*
35+
* @author Mike Eltsufin
36+
* @since 1.3
3637
*/
37-
public final class PublicClassMapper {
38+
public final class FirestoreDefaultClassMapper implements FirestoreClassMapper {
3839

3940
private static final Internal INTERNAL = new Internal(
4041
FirestoreOptions.newBuilder().setProjectId("dummy-project-id").build(), null);
@@ -43,21 +44,24 @@ public final class PublicClassMapper {
4344

4445
private static final String NOT_USED_PATH = "/not/used/path";
4546

46-
private PublicClassMapper() {
47+
public FirestoreDefaultClassMapper() {
4748
}
4849

49-
public static <T> Value convertToFirestoreValue(T entity) {
50+
public <T> Value toFirestoreValue(T sourceValue) {
5051
DocumentSnapshot documentSnapshot = INTERNAL.snapshotFromMap(NOT_USED_PATH,
51-
new MapBuilder<String, Object>().put(VALUE_FIELD_NAME, entity).build());
52+
new MapBuilder<String, Object>().put(VALUE_FIELD_NAME, sourceValue).build());
5253
return INTERNAL.protoFromSnapshot(documentSnapshot).get(VALUE_FIELD_NAME);
5354
}
5455

55-
public static <T> Map<String, Value> convertToFirestoreTypes(T entity) {
56+
public <T> Document entityToDocument(T entity, String documentResourceName) {
5657
DocumentSnapshot documentSnapshot = INTERNAL.snapshotFromObject(NOT_USED_PATH, entity);
57-
return INTERNAL.protoFromSnapshot(documentSnapshot);
58+
Map<String, Value> valuesMap = INTERNAL.protoFromSnapshot(documentSnapshot);
59+
return Document.newBuilder()
60+
.putAllFields(valuesMap)
61+
.setName(documentResourceName).build();
5862
}
5963

60-
public static <T> T convertToCustomClass(Document document, Class<T> clazz) {
64+
public <T> T documentToEntity(Document document, Class<T> clazz) {
6165
DocumentSnapshot documentSnapshot = INTERNAL.snapshotFromProto(Timestamp.now(), document);
6266
return documentSnapshot.toObject(clazz);
6367
}

spring-cloud-gcp-data-firestore/src/main/java/org/springframework/cloud/gcp/data/firestore/repository/query/PartTreeFirestoreQuery.java

+6-3
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@
2828
import org.springframework.cloud.gcp.core.util.MapBuilder;
2929
import org.springframework.cloud.gcp.data.firestore.FirestoreDataException;
3030
import org.springframework.cloud.gcp.data.firestore.FirestoreReactiveOperations;
31+
import org.springframework.cloud.gcp.data.firestore.mapping.FirestoreClassMapper;
3132
import org.springframework.cloud.gcp.data.firestore.mapping.FirestoreMappingContext;
3233
import org.springframework.cloud.gcp.data.firestore.mapping.FirestorePersistentEntity;
3334
import org.springframework.cloud.gcp.data.firestore.mapping.FirestorePersistentProperty;
34-
import org.springframework.cloud.gcp.data.firestore.mapping.PublicClassMapper;
3535
import org.springframework.data.domain.Pageable;
3636
import org.springframework.data.repository.query.ParameterAccessor;
3737
import org.springframework.data.repository.query.ParametersParameterAccessor;
@@ -62,6 +62,8 @@ public class PartTreeFirestoreQuery implements RepositoryQuery {
6262

6363
private final FirestorePersistentEntity<?> persistentEntity;
6464

65+
private final FirestoreClassMapper classMapper;
66+
6567
private static final Map<Part.Type, StructuredQuery.FieldFilter.Operator> PART_TO_FILTER_OP =
6668
new MapBuilder<Part.Type, StructuredQuery.FieldFilter.Operator>()
6769
.put(SIMPLE_PROPERTY, StructuredQuery.FieldFilter.Operator.EQUAL)
@@ -72,12 +74,13 @@ public class PartTreeFirestoreQuery implements RepositoryQuery {
7274
.build();
7375

7476
public PartTreeFirestoreQuery(FirestoreQueryMethod queryMethod, FirestoreReactiveOperations reactiveOperations,
75-
FirestoreMappingContext mappingContext) {
77+
FirestoreMappingContext mappingContext, FirestoreClassMapper classMapper) {
7678
this.queryMethod = queryMethod;
7779
this.reactiveOperations = reactiveOperations;
7880
ReturnedType returnedType = queryMethod.getResultProcessor().getReturnedType();
7981
this.tree = new PartTree(queryMethod.getName(), returnedType.getDomainType());
8082
this.persistentEntity = mappingContext.getPersistentEntity(returnedType.getDomainType());
83+
this.classMapper = classMapper;
8184
}
8285

8386
@Override
@@ -140,7 +143,7 @@ private StructuredQuery.Builder createBuilderWithFilter(Object[] parameters) {
140143
}
141144
filter.getFieldFilterBuilder().setField(fieldReference)
142145
.setOp(filterOp)
143-
.setValue(PublicClassMapper.convertToFirestoreValue(it.next()));
146+
.setValue(this.classMapper.toFirestoreValue(it.next()));
144147
}
145148
compositeFilter.addFilters(filter.build());
146149
});

spring-cloud-gcp-data-firestore/src/main/java/org/springframework/cloud/gcp/data/firestore/repository/support/FirestoreQueryLookupStrategy.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repository
5656
return new PartTreeFirestoreQuery(
5757
new FirestoreQueryMethod(method, repositoryMetadata, projectionFactory),
5858
this.firestoreTemplate,
59-
this.firestoreTemplate.getMappingContext());
59+
this.firestoreTemplate.getMappingContext(),
60+
this.firestoreTemplate.getClassMapper()
61+
);
6062
}
6163
}

spring-cloud-gcp-data-firestore/src/test/java/org/springframework/cloud/gcp/data/firestore/FirestoreTemplateTests.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
import reactor.core.publisher.Mono;
3636
import reactor.test.StepVerifier;
3737

38+
import org.springframework.cloud.gcp.data.firestore.mapping.FirestoreDefaultClassMapper;
39+
3840
import static org.mockito.ArgumentMatchers.any;
3941
import static org.mockito.ArgumentMatchers.eq;
4042
import static org.mockito.Mockito.doAnswer;
@@ -56,7 +58,8 @@ public class FirestoreTemplateTests {
5658

5759
@Before
5860
public void setup() {
59-
this.firestoreTemplate = new FirestoreTemplate(this.firestoreStub, this.parent);
61+
this.firestoreTemplate = new FirestoreTemplate(this.firestoreStub, this.parent,
62+
new FirestoreDefaultClassMapper());
6063
}
6164

6265
@Test

spring-cloud-gcp-data-firestore/src/test/java/org/springframework/cloud/gcp/data/firestore/it/FirestoreIntegrationTestsConfiguration.java

+11-3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import org.springframework.beans.factory.annotation.Value;
3030
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3131
import org.springframework.cloud.gcp.data.firestore.FirestoreTemplate;
32+
import org.springframework.cloud.gcp.data.firestore.mapping.FirestoreClassMapper;
33+
import org.springframework.cloud.gcp.data.firestore.mapping.FirestoreDefaultClassMapper;
3234
import org.springframework.cloud.gcp.data.firestore.mapping.FirestoreMappingContext;
3335
import org.springframework.cloud.gcp.data.firestore.repository.config.EnableReactiveFirestoreRepositories;
3436
import org.springframework.cloud.gcp.data.firestore.transaction.ReactiveFirestoreTransactionManager;
@@ -61,9 +63,9 @@ FirestoreGrpc.FirestoreStub firestoreStub() throws IOException {
6163
}
6264

6365
@Bean
64-
public FirestoreTemplate firestoreTemplate(FirestoreGrpc.FirestoreStub firestoreStub) {
65-
66-
return new FirestoreTemplate(firestoreStub, this.defaultParent);
66+
public FirestoreTemplate firestoreTemplate(FirestoreGrpc.FirestoreStub firestoreStub,
67+
FirestoreClassMapper classMapper) {
68+
return new FirestoreTemplate(firestoreStub, this.defaultParent, classMapper);
6769
}
6870

6971
@Bean
@@ -85,4 +87,10 @@ public UserService userService() {
8587
}
8688
//end::user_service_bean[]
8789

90+
91+
@Bean
92+
@ConditionalOnMissingBean
93+
public FirestoreClassMapper getClassMapper() {
94+
return new FirestoreDefaultClassMapper();
95+
}
8896
}

0 commit comments

Comments
 (0)