From 892dbb6153e68708b5fdd61f7ebfcdb8cf3205b3 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 19 Sep 2023 18:03:49 +0100 Subject: [PATCH 01/17] Try to reproduce `core-java#1536` in application to Datastore. --- .../datastore/DsProjectionColumnsTest.java | 174 ++++++++++++++++++ .../datastore/tenant/NamespaceIndexTest.java | 13 +- ...Projection.java => CollegeProjection.java} | 4 +- .../storage/datastore/SpyStorageFactory.java | 2 +- .../TestDatastoreStorageFactory.java | 28 ++- 5 files changed, 212 insertions(+), 9 deletions(-) create mode 100644 datastore/src/test/java/io/spine/server/storage/datastore/DsProjectionColumnsTest.java rename datastore/src/test/java/io/spine/server/storage/datastore/tenant/given/{TestProjection.java => CollegeProjection.java} (90%) diff --git a/datastore/src/test/java/io/spine/server/storage/datastore/DsProjectionColumnsTest.java b/datastore/src/test/java/io/spine/server/storage/datastore/DsProjectionColumnsTest.java new file mode 100644 index 00000000..32668600 --- /dev/null +++ b/datastore/src/test/java/io/spine/server/storage/datastore/DsProjectionColumnsTest.java @@ -0,0 +1,174 @@ +/* + * Copyright 2023, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.server.storage.datastore; + +import com.google.cloud.datastore.Entity; +import com.google.cloud.datastore.Key; +import com.google.cloud.datastore.NullValue; +import com.google.cloud.datastore.TimestampValue; +import com.google.cloud.datastore.Value; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.protobuf.Timestamp; +import com.google.protobuf.util.Durations; +import com.google.protobuf.util.Timestamps; +import io.spine.base.Time; +import io.spine.core.Version; +import io.spine.core.Versions; +import io.spine.protobuf.AnyPacker; +import io.spine.server.ContextSpec; +import io.spine.server.entity.EntityRecord; +import io.spine.server.entity.given.Given; +import io.spine.server.entity.storage.ColumnTypeMapping; +import io.spine.server.entity.storage.EntityRecordWithColumns; +import io.spine.server.projection.ProjectionStorage; +import io.spine.server.storage.datastore.tenant.given.CollegeProjection; +import io.spine.test.datastore.College; +import io.spine.test.datastore.CollegeId; +import io.spine.testing.server.storage.datastore.TestDatastoreStorageFactory; +import io.spine.type.TypeUrl; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static com.google.cloud.Timestamp.ofTimeSecondsAndNanos; +import static com.google.common.truth.Truth.assertThat; +import static io.spine.base.Identifier.newUuid; +import static io.spine.server.storage.datastore.RecordId.ofEntityId; +import static io.spine.server.storage.datastore.given.TestEnvironment.singleTenantSpec; +import static io.spine.testing.server.storage.datastore.TestDatastoreStorageFactory.local; + +@DisplayName("When dealing with `Projection` columns, `DsProjectionStorage` should") +final class DsProjectionColumnsTest { + + private static final TestDatastoreStorageFactory datastoreFactory = local(new CustomMapping()); + + @Test + @DisplayName("store Proto's `Timestamp` fields with the respect " + + "of Datastore's support of `Timestamps`") + void storeTimestamps() { + ContextSpec spec = singleTenantSpec(); + Class projectionCls = CollegeProjection.class; + ProjectionStorage storage = + datastoreFactory.createProjectionStorage(spec, projectionCls); + CollegeId id = CollegeId.newBuilder() + .setValue(newUuid()) + .vBuild(); + DatastoreWrapper datastore = datastoreFactory.createDatastoreWrapper(false); + + Kind collegeKind = Kind.of(TypeUrl.from(College.getDescriptor())); + Key key = datastore.keyFor(collegeKind, ofEntityId(id)); + + Timestamp admissionDeadline = Timestamps.add(Time.currentTime(), Durations.fromDays(100)); + College college = + College.newBuilder() + .setId(id) + .setName("Alma") + .setStudentCount(42) + .setAdmissionDeadline(admissionDeadline) + .setPassingGrade(4.2) + .setStateSponsored(false) + .setCreated(Time.currentTime()) + .addAllSubjects( + ImmutableList.of("English Literature", "Engineering", "Psychology")) + .vBuild(); + Version version = Versions.newVersion(42, Time.currentTime()); + EntityRecord record = EntityRecord + .newBuilder() + .setState(AnyPacker.pack(college)) + .setVersion(version) + .vBuild(); + CollegeProjection projection = + Given.projectionOfClass(CollegeProjection.class) + .withId(id) + .withState(college) + .withVersion(version.getNumber()) + .build(); + EntityRecordWithColumns recordWithCols = + EntityRecordWithColumns.create(record, projection, storage); + storage.write(id, recordWithCols); + Entity response = datastore.read(key); + + com.google.cloud.Timestamp storedDeadline = readAdmissionDeadline(response); + assertThat(storedDeadline).isNotNull(); + + // ------ + + College collegeNoAdmission = college.toBuilder() + .clearAdmissionDeadline() + .vBuild(); + CollegeProjection projectionNoAdmission = + Given.projectionOfClass(CollegeProjection.class) + .withId(id) + .withState(collegeNoAdmission) + .withVersion(version.getNumber()) + .build(); + EntityRecord recordNoAdmission = EntityRecord + .newBuilder() + .setState(AnyPacker.pack(collegeNoAdmission)) + .setVersion(version) + .vBuild(); + EntityRecordWithColumns recordWithColsNoAdmission = + EntityRecordWithColumns.create(recordNoAdmission, projectionNoAdmission, storage); + storage.write(id, recordWithColsNoAdmission); + + Entity responseWithNoAdmission = datastore.read(key); + com.google.cloud.Timestamp presumablyEmptyDeadline = + readAdmissionDeadline(responseWithNoAdmission); + assertThat(presumablyEmptyDeadline) + .isNull(); + } + + private static com.google.cloud.Timestamp readAdmissionDeadline(Entity response) { + com.google.cloud.Timestamp storedTimestamp = response.getTimestamp( + College.Column.admissionDeadline() + .name() + .value()); + return storedTimestamp; + } + + + private static final class CustomMapping extends DsColumnMapping { + + @Override + protected void setupCustomMapping( + ImmutableMap.Builder, ColumnTypeMapping>> builder) { + super.setupCustomMapping(builder); + builder.put(Timestamp.class, ofNullableTimestamp()); + } + + private static ColumnTypeMapping> ofNullableTimestamp() { + return timestamp -> { + if (timestamp.equals(Timestamp.getDefaultInstance())) { + return NullValue.of(); + } + return TimestampValue.of( + ofTimeSecondsAndNanos(timestamp.getSeconds(), timestamp.getNanos()) + ); + }; + } + } +} diff --git a/datastore/src/test/java/io/spine/server/storage/datastore/tenant/NamespaceIndexTest.java b/datastore/src/test/java/io/spine/server/storage/datastore/tenant/NamespaceIndexTest.java index 430468ae..463c81bf 100644 --- a/datastore/src/test/java/io/spine/server/storage/datastore/tenant/NamespaceIndexTest.java +++ b/datastore/src/test/java/io/spine/server/storage/datastore/tenant/NamespaceIndexTest.java @@ -41,8 +41,9 @@ import io.spine.server.entity.EntityRecord; import io.spine.server.storage.RecordStorage; import io.spine.server.storage.datastore.DatastoreStorageFactory; -import io.spine.server.storage.datastore.tenant.given.TestProjection; +import io.spine.server.storage.datastore.tenant.given.CollegeProjection; import io.spine.test.datastore.College; +import io.spine.test.datastore.CollegeId; import io.spine.testing.TestValues; import io.spine.testing.server.storage.datastore.TestDatastores; import org.junit.jupiter.api.AfterEach; @@ -174,14 +175,16 @@ void findPrefixedNamespaces() { .use(storageFactory); storageFactory.configureTenantIndex(contextBuilder); BoundedContext context = contextBuilder.build(); - RecordStorage storage = storageFactory - .createRecordStorage(context.spec(), TestProjection.class); - String id = "ABC"; + RecordStorage storage = storageFactory + .createRecordStorage(context.spec(), CollegeProjection.class); + CollegeId id = CollegeId.newBuilder() + .setValue("ABC") + .vBuild(); EntityRecord record = EntityRecord .newBuilder() .setEntityId(Identifier.pack(id)) .setState(pack(College.newBuilder() - .setName(id) + .setName(id.getValue()) .build())) .build(); TenantId tenantId = TenantId diff --git a/datastore/src/test/java/io/spine/server/storage/datastore/tenant/given/TestProjection.java b/datastore/src/test/java/io/spine/server/storage/datastore/tenant/given/CollegeProjection.java similarity index 90% rename from datastore/src/test/java/io/spine/server/storage/datastore/tenant/given/TestProjection.java rename to datastore/src/test/java/io/spine/server/storage/datastore/tenant/given/CollegeProjection.java index 76f67a60..966439dc 100644 --- a/datastore/src/test/java/io/spine/server/storage/datastore/tenant/given/TestProjection.java +++ b/datastore/src/test/java/io/spine/server/storage/datastore/tenant/given/CollegeProjection.java @@ -28,6 +28,8 @@ import io.spine.server.projection.Projection; import io.spine.test.datastore.College; +import io.spine.test.datastore.CollegeId; -public final class TestProjection extends Projection { +public final class CollegeProjection + extends Projection { } diff --git a/testutil-gcloud/src/main/java/io/spine/testing/server/storage/datastore/SpyStorageFactory.java b/testutil-gcloud/src/main/java/io/spine/testing/server/storage/datastore/SpyStorageFactory.java index 4689fed8..1632e2b1 100644 --- a/testutil-gcloud/src/main/java/io/spine/testing/server/storage/datastore/SpyStorageFactory.java +++ b/testutil-gcloud/src/main/java/io/spine/testing/server/storage/datastore/SpyStorageFactory.java @@ -57,7 +57,7 @@ public SpyStorageFactory() { } @Override - protected DatastoreWrapper createDatastoreWrapper(boolean multitenant) { + public DatastoreWrapper createDatastoreWrapper(boolean multitenant) { return injectedWrapper; } diff --git a/testutil-gcloud/src/main/java/io/spine/testing/server/storage/datastore/TestDatastoreStorageFactory.java b/testutil-gcloud/src/main/java/io/spine/testing/server/storage/datastore/TestDatastoreStorageFactory.java index dd4faf99..48fa1ecc 100644 --- a/testutil-gcloud/src/main/java/io/spine/testing/server/storage/datastore/TestDatastoreStorageFactory.java +++ b/testutil-gcloud/src/main/java/io/spine/testing/server/storage/datastore/TestDatastoreStorageFactory.java @@ -27,9 +27,11 @@ package io.spine.testing.server.storage.datastore; import com.google.cloud.datastore.Datastore; +import com.google.cloud.datastore.Value; import com.google.common.collect.ImmutableSet; import com.google.common.flogger.FluentLogger; import io.spine.annotation.Internal; +import io.spine.server.entity.storage.ColumnMapping; import io.spine.server.storage.datastore.DatastoreStorageFactory; import io.spine.server.storage.datastore.DatastoreWrapper; import io.spine.server.storage.datastore.DsColumnMapping; @@ -52,10 +54,14 @@ public class TestDatastoreStorageFactory extends DatastoreStorageFactory { private final Collection allCreatedWrappers = new HashSet<>(); protected TestDatastoreStorageFactory(Datastore datastore) { + this(datastore, new DsColumnMapping()); + } + + protected TestDatastoreStorageFactory(Datastore datastore, ColumnMapping> mapping) { super(DatastoreStorageFactory .newBuilder() .setDatastore(datastore) - .setColumnMapping(new DsColumnMapping()) + .setColumnMapping(mapping) ); } @@ -68,6 +74,15 @@ public static TestDatastoreStorageFactory local() { return basedOn(TestDatastores.local()); } + /** + * Creates a new instance which works with a local Datastore emulator. + * + *

A shortcut for {@code basedOn(TestDatastores.local())}. + */ + public static TestDatastoreStorageFactory local(ColumnMapping mapping) { + return basedOn(TestDatastores.local(), mapping); + } + /** * Creates a new factory instance which wraps the given Datastore. */ @@ -76,9 +91,18 @@ public static TestDatastoreStorageFactory basedOn(Datastore datastore) { return new TestDatastoreStorageFactory(datastore); } + /** + * Creates a new factory instance which wraps the given Datastore. + */ + private static TestDatastoreStorageFactory + basedOn(Datastore datastore, ColumnMapping mapping) { + checkNotNull(datastore); + return new TestDatastoreStorageFactory(datastore); + } + @Internal @Override - protected DatastoreWrapper createDatastoreWrapper(boolean multitenant) { + public DatastoreWrapper createDatastoreWrapper(boolean multitenant) { TestDatastoreWrapper wrapper = TestDatastoreWrapper.wrap(datastore(), false); allCreatedWrappers.add(wrapper); return wrapper; From 082e1a9bdece39f0f621b44904645f6b3e807ec5 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 19 Sep 2023 18:04:19 +0100 Subject: [PATCH 02/17] Suppress a warning. --- .../spine/server/storage/datastore/DsProjectionColumnsTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/datastore/src/test/java/io/spine/server/storage/datastore/DsProjectionColumnsTest.java b/datastore/src/test/java/io/spine/server/storage/datastore/DsProjectionColumnsTest.java index 32668600..bafd8278 100644 --- a/datastore/src/test/java/io/spine/server/storage/datastore/DsProjectionColumnsTest.java +++ b/datastore/src/test/java/io/spine/server/storage/datastore/DsProjectionColumnsTest.java @@ -160,6 +160,7 @@ protected void setupCustomMapping( builder.put(Timestamp.class, ofNullableTimestamp()); } + @SuppressWarnings("UnnecessaryLambda") private static ColumnTypeMapping> ofNullableTimestamp() { return timestamp -> { if (timestamp.equals(Timestamp.getDefaultInstance())) { From fb465f3019aa9a702775212de4c6c9186f5208d9 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 19 Sep 2023 18:17:05 +0100 Subject: [PATCH 03/17] Address compilation and runtime issues. Set a proper name for the test. --- .../server/storage/datastore/DsColumnMapping.java | 6 +++--- .../storage/datastore/DsProjectionColumnsTest.java | 13 +++++++------ .../datastore/TestDatastoreStorageFactory.java | 6 +++--- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/datastore/src/main/java/io/spine/server/storage/datastore/DsColumnMapping.java b/datastore/src/main/java/io/spine/server/storage/datastore/DsColumnMapping.java index 847a2cda..8c0aa0fe 100644 --- a/datastore/src/main/java/io/spine/server/storage/datastore/DsColumnMapping.java +++ b/datastore/src/main/java/io/spine/server/storage/datastore/DsColumnMapping.java @@ -120,16 +120,16 @@ protected ColumnTypeMapping ofMessage() { return o -> NullValue.of(); } - @SuppressWarnings({"ProtoTimestampGetSecondsGetNano", "UnnecessaryLambda"}) + @SuppressWarnings({"ProtoTimestampGetSecondsGetNano", "UnnecessaryLambda", "WeakerAccess"}) // This behavior is intended. - private static ColumnTypeMapping ofTimestamp() { + protected static ColumnTypeMapping ofTimestamp() { return timestamp -> TimestampValue.of( ofTimeSecondsAndNanos(timestamp.getSeconds(), timestamp.getNanos()) ); } @SuppressWarnings("UnnecessaryLambda") - private static ColumnTypeMapping ofVersion() { + protected static ColumnTypeMapping ofVersion() { return version -> LongValue.of(version.getNumber()); } } diff --git a/datastore/src/test/java/io/spine/server/storage/datastore/DsProjectionColumnsTest.java b/datastore/src/test/java/io/spine/server/storage/datastore/DsProjectionColumnsTest.java index bafd8278..e1df4c52 100644 --- a/datastore/src/test/java/io/spine/server/storage/datastore/DsProjectionColumnsTest.java +++ b/datastore/src/test/java/io/spine/server/storage/datastore/DsProjectionColumnsTest.java @@ -67,9 +67,9 @@ final class DsProjectionColumnsTest { private static final TestDatastoreStorageFactory datastoreFactory = local(new CustomMapping()); @Test - @DisplayName("store Proto's `Timestamp` fields with the respect " + - "of Datastore's support of `Timestamps`") - void storeTimestamps() { + @DisplayName("allow clearing the column values, if `null` is returned as Datastore `Value<..>`" + + "via custom column mapping") + void clearTimestampColumns() { ContextSpec spec = singleTenantSpec(); Class projectionCls = CollegeProjection.class; ProjectionStorage storage = @@ -154,10 +154,11 @@ private static com.google.cloud.Timestamp readAdmissionDeadline(Entity response) private static final class CustomMapping extends DsColumnMapping { @Override - protected void setupCustomMapping( - ImmutableMap.Builder, ColumnTypeMapping>> builder) { - super.setupCustomMapping(builder); + protected void + setupCustomMapping(ImmutableMap.Builder, + ColumnTypeMapping>> builder) { builder.put(Timestamp.class, ofNullableTimestamp()); + builder.put(Version.class, ofVersion()); } @SuppressWarnings("UnnecessaryLambda") diff --git a/testutil-gcloud/src/main/java/io/spine/testing/server/storage/datastore/TestDatastoreStorageFactory.java b/testutil-gcloud/src/main/java/io/spine/testing/server/storage/datastore/TestDatastoreStorageFactory.java index 48fa1ecc..9e6b6cec 100644 --- a/testutil-gcloud/src/main/java/io/spine/testing/server/storage/datastore/TestDatastoreStorageFactory.java +++ b/testutil-gcloud/src/main/java/io/spine/testing/server/storage/datastore/TestDatastoreStorageFactory.java @@ -79,7 +79,7 @@ public static TestDatastoreStorageFactory local() { * *

A shortcut for {@code basedOn(TestDatastores.local())}. */ - public static TestDatastoreStorageFactory local(ColumnMapping mapping) { + public static TestDatastoreStorageFactory local(ColumnMapping> mapping) { return basedOn(TestDatastores.local(), mapping); } @@ -95,9 +95,9 @@ public static TestDatastoreStorageFactory basedOn(Datastore datastore) { * Creates a new factory instance which wraps the given Datastore. */ private static TestDatastoreStorageFactory - basedOn(Datastore datastore, ColumnMapping mapping) { + basedOn(Datastore datastore, ColumnMapping> mapping) { checkNotNull(datastore); - return new TestDatastoreStorageFactory(datastore); + return new TestDatastoreStorageFactory(datastore, mapping); } @Internal From 30200c19dba83c560e291de3babf029d8388151b Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 22 Sep 2023 14:20:06 +0100 Subject: [PATCH 04/17] Bump the version -> `1.9.1`. --- README.md | 8 ++++---- version.gradle.kts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3f6780b9..a79bab99 100644 --- a/README.md +++ b/README.md @@ -16,16 +16,16 @@ Gradle: ```kotlin dependencies { // Datastore Storage support library. - implementation("io.spine.gcloud:spine-datastore:1.8.0") + implementation("io.spine.gcloud:spine-datastore:1.9.1") // Pub/Sub messaging support library. - implementation("io.spine.gcloud:spine-pubsub:1.8.0") + implementation("io.spine.gcloud:spine-pubsub:1.9.1") // Stackdriver Trace support library. - implementation("io.spine.gcloud:spine-stackdriver-trace:1.8.0") + implementation("io.spine.gcloud:spine-stackdriver-trace:1.9.1") // Datastore-related test utilities (if needed). - implementation("io.spine.gcloud:testutil-gcloud:1.8.0") + implementation("io.spine.gcloud:testutil-gcloud:1.9.1") } ``` diff --git a/version.gradle.kts b/version.gradle.kts index 9e19e396..c4de9fe4 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -37,4 +37,4 @@ val cloudTraceVersion: String by extra("2.14.0") val spineBaseVersion: String by extra("1.9.0") val spineCoreVersion: String by extra("1.9.0") -val versionToPublish: String by extra("1.9.0") +val versionToPublish: String by extra("1.9.1") From 172a6b8037815cf804bd13bc88a7da082395cdb9 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 22 Sep 2023 16:16:09 +0100 Subject: [PATCH 05/17] Update the report files. --- license-report.md | 16 ++++++++-------- pom.xml | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/license-report.md b/license-report.md index 3ec3e4d0..99da4fa0 100644 --- a/license-report.md +++ b/license-report.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine.gcloud:spine-datastore:1.9.0` +# Dependencies of `io.spine.gcloud:spine-datastore:1.9.1` ## Runtime 1. **Group:** com.fasterxml.jackson **Name:** jackson-bom **Version:** 2.14.2 **No license information found** @@ -722,12 +722,12 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri May 19 11:08:42 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri Sep 22 14:21:53 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.gcloud:spine-pubsub:1.9.0` +# Dependencies of `io.spine.gcloud:spine-pubsub:1.9.1` ## Runtime 1. **Group:** com.google.android **Name:** annotations **Version:** 4.1.1.4 @@ -1149,12 +1149,12 @@ This report was generated on **Fri May 19 11:08:42 WEST 2023** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri May 19 11:08:54 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri Sep 22 14:22:02 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.gcloud:spine-stackdriver-trace:1.9.0` +# Dependencies of `io.spine.gcloud:spine-stackdriver-trace:1.9.1` ## Runtime 1. **Group:** com.google.android **Name:** annotations **Version:** 4.1.1.4 @@ -1784,12 +1784,12 @@ This report was generated on **Fri May 19 11:08:54 WEST 2023** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri May 19 11:09:10 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri Sep 22 14:22:15 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.gcloud:spine-testutil-gcloud:1.9.0` +# Dependencies of `io.spine.gcloud:spine-testutil-gcloud:1.9.1` ## Runtime 1. **Group:** com.fasterxml.jackson **Name:** jackson-bom **Version:** 2.14.2 **No license information found** @@ -2511,4 +2511,4 @@ This report was generated on **Fri May 19 11:09:10 WEST 2023** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri May 19 11:09:19 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file +This report was generated on **Fri Sep 22 14:22:23 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file diff --git a/pom.xml b/pom.xml index 62d27698..ee410ae1 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ all modules and does not describe the project structure per-subproject. io.spine.gcloud spine-gcloud-java -1.9.0 +1.9.1 2015 From 75c9bdb578678f9dc97c40e3bea7afb640397a7a Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 22 Sep 2023 16:36:44 +0100 Subject: [PATCH 06/17] Update the key, as the previous one was demoted. --- .github/keys/spine-dev-framework-ci.json.gpg | Bin 0 -> 1739 bytes .github/keys/spine-dev.json.gpg | Bin 1719 -> 0 bytes .github/workflows/build-on-ubuntu.yml | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 .github/keys/spine-dev-framework-ci.json.gpg delete mode 100644 .github/keys/spine-dev.json.gpg diff --git a/.github/keys/spine-dev-framework-ci.json.gpg b/.github/keys/spine-dev-framework-ci.json.gpg new file mode 100644 index 0000000000000000000000000000000000000000..3041f37ece646021035272d7980f6b0ada6efa60 GIT binary patch literal 1739 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 N!2QC#hK<#d!8jwpVxa&4 diff --git a/.github/workflows/build-on-ubuntu.yml b/.github/workflows/build-on-ubuntu.yml index e77b63c5..d7e73081 100644 --- a/.github/workflows/build-on-ubuntu.yml +++ b/.github/workflows/build-on-ubuntu.yml @@ -19,9 +19,9 @@ jobs: # This operation is specific to `gcloud-java` repository only. - name: Decrypt the credentials for the Spine-Dev service account - run: ./scripts/decrypt.sh "$SPINE_DEV_KEY" ./.github/keys/spine-dev.json.gpg ./spine-dev.json + run: ./config/scripts/decrypt.sh "$SPINE_DEV_CI_KEY" ./.github/keys/spine-dev-framework-ci.json.gpg ./spine-dev.json env: - SPINE_DEV_KEY: ${{ secrets.SPINE_DEV_KEY }} + SPINE_DEV_CI_KEY: ${{ secrets.SPINE_DEV_CI_KEY }} # The OS-managed Google Cloud SDK does not provide a Datastore emulator. - name: Remove the OS-managed Google Cloud SDK From 11b1803d69029d4b69c444146ed9086d327ceebe Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 22 Sep 2023 17:09:24 +0100 Subject: [PATCH 07/17] Update the key, as the previous one was demoted. --- .../datastore/DsProjectionColumnsTest.java | 137 +++----------- .../given/DsProjectionColumnsTestEnv.java | 178 ++++++++++++++++++ 2 files changed, 202 insertions(+), 113 deletions(-) create mode 100644 datastore/src/test/java/io/spine/server/storage/datastore/given/DsProjectionColumnsTestEnv.java diff --git a/datastore/src/test/java/io/spine/server/storage/datastore/DsProjectionColumnsTest.java b/datastore/src/test/java/io/spine/server/storage/datastore/DsProjectionColumnsTest.java index e1df4c52..c911effd 100644 --- a/datastore/src/test/java/io/spine/server/storage/datastore/DsProjectionColumnsTest.java +++ b/datastore/src/test/java/io/spine/server/storage/datastore/DsProjectionColumnsTest.java @@ -26,38 +26,28 @@ package io.spine.server.storage.datastore; -import com.google.cloud.datastore.Entity; import com.google.cloud.datastore.Key; -import com.google.cloud.datastore.NullValue; -import com.google.cloud.datastore.TimestampValue; -import com.google.cloud.datastore.Value; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.protobuf.Timestamp; -import com.google.protobuf.util.Durations; -import com.google.protobuf.util.Timestamps; -import io.spine.base.Time; import io.spine.core.Version; -import io.spine.core.Versions; -import io.spine.protobuf.AnyPacker; import io.spine.server.ContextSpec; -import io.spine.server.entity.EntityRecord; -import io.spine.server.entity.given.Given; -import io.spine.server.entity.storage.ColumnTypeMapping; -import io.spine.server.entity.storage.EntityRecordWithColumns; import io.spine.server.projection.ProjectionStorage; -import io.spine.server.storage.datastore.tenant.given.CollegeProjection; +import io.spine.server.storage.datastore.given.DsProjectionColumnsTestEnv.CustomMapping; import io.spine.test.datastore.College; import io.spine.test.datastore.CollegeId; import io.spine.testing.server.storage.datastore.TestDatastoreStorageFactory; -import io.spine.type.TypeUrl; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import static com.google.cloud.Timestamp.ofTimeSecondsAndNanos; import static com.google.common.truth.Truth.assertThat; -import static io.spine.base.Identifier.newUuid; import static io.spine.server.storage.datastore.RecordId.ofEntityId; +import static io.spine.server.storage.datastore.given.DsProjectionColumnsTestEnv.COLLEGE_CLS; +import static io.spine.server.storage.datastore.given.DsProjectionColumnsTestEnv.COLLEGE_KIND; +import static io.spine.server.storage.datastore.given.DsProjectionColumnsTestEnv.clearAdmission; +import static io.spine.server.storage.datastore.given.DsProjectionColumnsTestEnv.futureFromNow; +import static io.spine.server.storage.datastore.given.DsProjectionColumnsTestEnv.newCollege; +import static io.spine.server.storage.datastore.given.DsProjectionColumnsTestEnv.newId; +import static io.spine.server.storage.datastore.given.DsProjectionColumnsTestEnv.someVersion; +import static io.spine.server.storage.datastore.given.DsProjectionColumnsTestEnv.writeAndReadDeadline; import static io.spine.server.storage.datastore.given.TestEnvironment.singleTenantSpec; import static io.spine.testing.server.storage.datastore.TestDatastoreStorageFactory.local; @@ -67,110 +57,31 @@ final class DsProjectionColumnsTest { private static final TestDatastoreStorageFactory datastoreFactory = local(new CustomMapping()); @Test - @DisplayName("allow clearing the column values, if `null` is returned as Datastore `Value<..>`" + - "via custom column mapping") + @DisplayName( + "allow clearing the column values if they get Proto's default values, " + + "by having a custom column mapping " + + "returning Datastore's `null` for respective values") void clearTimestampColumns() { ContextSpec spec = singleTenantSpec(); - Class projectionCls = CollegeProjection.class; ProjectionStorage storage = - datastoreFactory.createProjectionStorage(spec, projectionCls); - CollegeId id = CollegeId.newBuilder() - .setValue(newUuid()) - .vBuild(); + datastoreFactory.createProjectionStorage(spec, COLLEGE_CLS); DatastoreWrapper datastore = datastoreFactory.createDatastoreWrapper(false); + + CollegeId id = newId(); + Key key = datastore.keyFor(COLLEGE_KIND, ofEntityId(id)); + Version version = someVersion(); - Kind collegeKind = Kind.of(TypeUrl.from(College.getDescriptor())); - Key key = datastore.keyFor(collegeKind, ofEntityId(id)); + Timestamp admissionDeadline = futureFromNow(); + College college = newCollege(id, admissionDeadline); - Timestamp admissionDeadline = Timestamps.add(Time.currentTime(), Durations.fromDays(100)); - College college = - College.newBuilder() - .setId(id) - .setName("Alma") - .setStudentCount(42) - .setAdmissionDeadline(admissionDeadline) - .setPassingGrade(4.2) - .setStateSponsored(false) - .setCreated(Time.currentTime()) - .addAllSubjects( - ImmutableList.of("English Literature", "Engineering", "Psychology")) - .vBuild(); - Version version = Versions.newVersion(42, Time.currentTime()); - EntityRecord record = EntityRecord - .newBuilder() - .setState(AnyPacker.pack(college)) - .setVersion(version) - .vBuild(); - CollegeProjection projection = - Given.projectionOfClass(CollegeProjection.class) - .withId(id) - .withState(college) - .withVersion(version.getNumber()) - .build(); - EntityRecordWithColumns recordWithCols = - EntityRecordWithColumns.create(record, projection, storage); - storage.write(id, recordWithCols); - Entity response = datastore.read(key); - - com.google.cloud.Timestamp storedDeadline = readAdmissionDeadline(response); + com.google.cloud.Timestamp storedDeadline = + writeAndReadDeadline(college, version, storage, datastore, key); assertThat(storedDeadline).isNotNull(); - // ------ - - College collegeNoAdmission = college.toBuilder() - .clearAdmissionDeadline() - .vBuild(); - CollegeProjection projectionNoAdmission = - Given.projectionOfClass(CollegeProjection.class) - .withId(id) - .withState(collegeNoAdmission) - .withVersion(version.getNumber()) - .build(); - EntityRecord recordNoAdmission = EntityRecord - .newBuilder() - .setState(AnyPacker.pack(collegeNoAdmission)) - .setVersion(version) - .vBuild(); - EntityRecordWithColumns recordWithColsNoAdmission = - EntityRecordWithColumns.create(recordNoAdmission, projectionNoAdmission, storage); - storage.write(id, recordWithColsNoAdmission); - - Entity responseWithNoAdmission = datastore.read(key); + College collegeNoAdmission = clearAdmission(college); com.google.cloud.Timestamp presumablyEmptyDeadline = - readAdmissionDeadline(responseWithNoAdmission); + writeAndReadDeadline(collegeNoAdmission, version, storage, datastore, key); assertThat(presumablyEmptyDeadline) .isNull(); } - - private static com.google.cloud.Timestamp readAdmissionDeadline(Entity response) { - com.google.cloud.Timestamp storedTimestamp = response.getTimestamp( - College.Column.admissionDeadline() - .name() - .value()); - return storedTimestamp; - } - - - private static final class CustomMapping extends DsColumnMapping { - - @Override - protected void - setupCustomMapping(ImmutableMap.Builder, - ColumnTypeMapping>> builder) { - builder.put(Timestamp.class, ofNullableTimestamp()); - builder.put(Version.class, ofVersion()); - } - - @SuppressWarnings("UnnecessaryLambda") - private static ColumnTypeMapping> ofNullableTimestamp() { - return timestamp -> { - if (timestamp.equals(Timestamp.getDefaultInstance())) { - return NullValue.of(); - } - return TimestampValue.of( - ofTimeSecondsAndNanos(timestamp.getSeconds(), timestamp.getNanos()) - ); - }; - } - } } diff --git a/datastore/src/test/java/io/spine/server/storage/datastore/given/DsProjectionColumnsTestEnv.java b/datastore/src/test/java/io/spine/server/storage/datastore/given/DsProjectionColumnsTestEnv.java new file mode 100644 index 00000000..14635853 --- /dev/null +++ b/datastore/src/test/java/io/spine/server/storage/datastore/given/DsProjectionColumnsTestEnv.java @@ -0,0 +1,178 @@ +/* + * Copyright 2023, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.server.storage.datastore.given; + +import com.google.cloud.datastore.Entity; +import com.google.cloud.datastore.Key; +import com.google.cloud.datastore.NullValue; +import com.google.cloud.datastore.TimestampValue; +import com.google.cloud.datastore.Value; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.protobuf.Timestamp; +import com.google.protobuf.util.Durations; +import com.google.protobuf.util.Timestamps; +import io.spine.base.Time; +import io.spine.core.Version; +import io.spine.core.Versions; +import io.spine.protobuf.AnyPacker; +import io.spine.server.entity.EntityRecord; +import io.spine.server.entity.given.Given; +import io.spine.server.entity.storage.ColumnTypeMapping; +import io.spine.server.entity.storage.EntityRecordWithColumns; +import io.spine.server.projection.ProjectionStorage; +import io.spine.server.storage.datastore.DatastoreWrapper; +import io.spine.server.storage.datastore.DsColumnMapping; +import io.spine.server.storage.datastore.Kind; +import io.spine.server.storage.datastore.tenant.given.CollegeProjection; +import io.spine.test.datastore.College; +import io.spine.test.datastore.CollegeId; +import io.spine.type.TypeUrl; + +import static com.google.cloud.Timestamp.ofTimeSecondsAndNanos; +import static io.spine.base.Identifier.newUuid; + +/** + * A test environment for {@link DsProjectionColumnsTest}. + */ +public final class DsProjectionColumnsTestEnv { + + public static final Class COLLEGE_CLS = CollegeProjection.class; + public static final Kind COLLEGE_KIND = Kind.of(TypeUrl.from(College.getDescriptor())); + + /** + * Prevent this test environment from instantiation. + */ + private DsProjectionColumnsTestEnv() { + } + + public static com.google.cloud.Timestamp + writeAndReadDeadline(College college, + Version version, + ProjectionStorage storage, + DatastoreWrapper datastore, + Key key) { + EntityRecord record = toEntityRecord(college, version); + CollegeProjection projection = entityWith(college, version); + EntityRecordWithColumns recordWithCols = + EntityRecordWithColumns.create(record, projection, storage); + storage.write(college.getId(), recordWithCols); + Entity response = datastore.read(key); + com.google.cloud.Timestamp storedDeadline = readAdmissionDeadline(response); + return storedDeadline; + } + + public static College newCollege(CollegeId id, Timestamp admissionDeadline) { + College college = + College.newBuilder() + .setId(id) + .setName("Alma") + .setStudentCount(42) + .setAdmissionDeadline(admissionDeadline) + .setPassingGrade(4.2) + .setStateSponsored(false) + .setCreated(Time.currentTime()) + .addAllSubjects( + ImmutableList.of("English Literature", "Engineering", "Psychology")) + .vBuild(); + return college; + } + + private static EntityRecord toEntityRecord(College collegeNoAdmission, Version version) { + EntityRecord recordNoAdmission = EntityRecord + .newBuilder() + .setState(AnyPacker.pack(collegeNoAdmission)) + .setVersion(version) + .vBuild(); + return recordNoAdmission; + } + + public static College clearAdmission(College college) { + return college.toBuilder() + .clearAdmissionDeadline() + .vBuild(); + } + + private static CollegeProjection entityWith(College state, Version version) { + CollegeProjection projection = + Given.projectionOfClass(CollegeProjection.class) + .withId(state.getId()) + .withState(state) + .withVersion(version.getNumber()) + .build(); + return projection; + } + + public static Version someVersion() { + return Versions.newVersion(42, Time.currentTime()); + } + + public static Timestamp futureFromNow() { + return Timestamps.add(Time.currentTime(), Durations.fromDays(100)); + } + + public static CollegeId newId() { + return CollegeId.newBuilder() + .setValue(newUuid()) + .vBuild(); + } + + private static com.google.cloud.Timestamp readAdmissionDeadline(Entity response) { + com.google.cloud.Timestamp storedTimestamp = response.getTimestamp( + College.Column.admissionDeadline() + .name() + .value()); + return storedTimestamp; + } + + /** + * A mapping similar to the default one, + * but telling to store {@link Timestamp}s as {@code null}s. + */ + public static final class CustomMapping extends DsColumnMapping { + + @Override + protected void + setupCustomMapping(ImmutableMap.Builder, + ColumnTypeMapping>> builder) { + builder.put(Timestamp.class, ofNullableTimestamp()) + .put(Version.class, ofVersion()); + } + + @SuppressWarnings("UnnecessaryLambda") + private static ColumnTypeMapping> ofNullableTimestamp() { + return timestamp -> { + if (timestamp.equals(Timestamp.getDefaultInstance())) { + return NullValue.of(); + } + return TimestampValue.of( + ofTimeSecondsAndNanos(timestamp.getSeconds(), timestamp.getNanos()) + ); + }; + } + } +} From 26c28b4aec8d64758ddcf058628bfdde9f1ce6e7 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Sun, 24 Sep 2023 16:39:42 +0100 Subject: [PATCH 08/17] Document the API change. --- .../storage/datastore/DsColumnMapping.java | 68 +++++++++++++++++-- 1 file changed, 61 insertions(+), 7 deletions(-) diff --git a/datastore/src/main/java/io/spine/server/storage/datastore/DsColumnMapping.java b/datastore/src/main/java/io/spine/server/storage/datastore/DsColumnMapping.java index 8c0aa0fe..d62a491b 100644 --- a/datastore/src/main/java/io/spine/server/storage/datastore/DsColumnMapping.java +++ b/datastore/src/main/java/io/spine/server/storage/datastore/DsColumnMapping.java @@ -39,11 +39,16 @@ import com.google.protobuf.ByteString; import com.google.protobuf.Message; import com.google.protobuf.Timestamp; +import io.spine.annotation.SPI; import io.spine.core.Version; import io.spine.server.entity.storage.AbstractColumnMapping; +import io.spine.server.entity.storage.ColumnMapping; import io.spine.server.entity.storage.ColumnTypeMapping; import io.spine.string.Stringifiers; +import java.util.HashMap; +import java.util.Map; + import static com.google.cloud.Timestamp.ofTimeSecondsAndNanos; /** @@ -52,16 +57,57 @@ *

All column values are stored as Datastore {@link Value}-s. * *

Users of the storage can extend this class to specify their own column mapping for the - * selected types. + * selected types. See {@link DsColumnMapping#customMapping() DsColumnMapping.customMapping()}. + * + * @see DatastoreStorageFactory.Builder#setColumnMapping(ColumnMapping) */ public class DsColumnMapping extends AbstractColumnMapping> { + private static final Map, ColumnTypeMapping>> defaults + = ImmutableMap.of(Timestamp.class, ofTimestamp(), + Version.class, ofVersion()); + + /** + * {@inheritDoc} + * + *

Merges the default column mapping rules with those provided by SPI users. + * In case there are duplicate mappings for some column type, the value provided + * by SPI users is used. + * + * @apiNote This method is made {@code final}, as it is designed + * to use {@code ImmutableMap.Builder}, which does not allow to override values. + * Therefore, it is not possible for SPI users to provide their own mapping rules + * for types such as {@code Timestamp}, for which this class already has + * a default mapping. SPI users should override + * {@link #customMapping() DsColumnMapping.customMapping()} instead. + */ @Override - protected void + protected final void setupCustomMapping( ImmutableMap.Builder, ColumnTypeMapping>> builder) { - builder.put(Timestamp.class, ofTimestamp()); - builder.put(Version.class, ofVersion()); + Map, ColumnTypeMapping>> merged = new HashMap<>(); + ImmutableMap, ColumnTypeMapping>> custom = customMapping(); + merged.putAll(defaults); + merged.putAll(custom); + builder.putAll(merged); + } + + /** + * Returns the custom column mapping rules. + * + *

This method is designed for SPI users in order to be able to re-define + * and-or append their custom mapping. As by default, {@code DsColumnMapping} + * provides rules for {@link Timestamp} and {@link Version}, SPI users may + * choose to either override these defaults by returning their own mapping for these types, + * or supply even more mapping rules. + * + *

By default, this method returns an empty map. + * + * @return custom column mappings, per Java class of column + */ + @SPI + protected ImmutableMap, ColumnTypeMapping>> customMapping() { + return ImmutableMap.of(); } @Override @@ -120,15 +166,23 @@ protected ColumnTypeMapping ofMessage() { return o -> NullValue.of(); } - @SuppressWarnings({"ProtoTimestampGetSecondsGetNano", "UnnecessaryLambda", "WeakerAccess"}) - // This behavior is intended. + /** + * Returns the default mapping from {@link Timestamp} to {@link TimestampValue}. + */ + @SuppressWarnings({"ProtoTimestampGetSecondsGetNano" /* In order to create exact value. */, + "UnnecessaryLambda" /* For brevity.*/, + "WeakerAccess" /* To allow access for SPI users. */}) protected static ColumnTypeMapping ofTimestamp() { return timestamp -> TimestampValue.of( ofTimeSecondsAndNanos(timestamp.getSeconds(), timestamp.getNanos()) ); } - @SuppressWarnings("UnnecessaryLambda") + /** + * Returns the default mapping from {@link Version} to {@link LongValue}. + */ + @SuppressWarnings({"UnnecessaryLambda" /* For brevity.*/, + "WeakerAccess" /* To allow access for SPI users. */}) protected static ColumnTypeMapping ofVersion() { return version -> LongValue.of(version.getNumber()); } From 156d20bd0753db3d4bed955f1aae6753a97ae76c Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Sun, 24 Sep 2023 16:40:02 +0100 Subject: [PATCH 09/17] Adjust the usage of the updated API endpoint. --- .../datastore/given/DsProjectionColumnsTestEnv.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/datastore/src/test/java/io/spine/server/storage/datastore/given/DsProjectionColumnsTestEnv.java b/datastore/src/test/java/io/spine/server/storage/datastore/given/DsProjectionColumnsTestEnv.java index 14635853..52f0f1d7 100644 --- a/datastore/src/test/java/io/spine/server/storage/datastore/given/DsProjectionColumnsTestEnv.java +++ b/datastore/src/test/java/io/spine/server/storage/datastore/given/DsProjectionColumnsTestEnv.java @@ -156,11 +156,8 @@ private static com.google.cloud.Timestamp readAdmissionDeadline(Entity response) public static final class CustomMapping extends DsColumnMapping { @Override - protected void - setupCustomMapping(ImmutableMap.Builder, - ColumnTypeMapping>> builder) { - builder.put(Timestamp.class, ofNullableTimestamp()) - .put(Version.class, ofVersion()); + protected ImmutableMap, ColumnTypeMapping>> customMapping() { + return ImmutableMap.of(Timestamp.class, ofNullableTimestamp()); } @SuppressWarnings("UnnecessaryLambda") From d00b436eb4e7bff9ef72724f02a05a45b55d8561 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Sun, 24 Sep 2023 16:48:51 +0100 Subject: [PATCH 10/17] Update the report file. --- license-report.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/license-report.md b/license-report.md index 99da4fa0..16043fc1 100644 --- a/license-report.md +++ b/license-report.md @@ -722,7 +722,7 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Sep 22 14:21:53 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Sun Sep 24 16:41:14 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). @@ -1149,7 +1149,7 @@ This report was generated on **Fri Sep 22 14:21:53 WEST 2023** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Sep 22 14:22:02 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Sun Sep 24 16:41:22 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). @@ -1784,7 +1784,7 @@ This report was generated on **Fri Sep 22 14:22:02 WEST 2023** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Sep 22 14:22:15 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Sun Sep 24 16:41:35 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). @@ -2511,4 +2511,4 @@ This report was generated on **Fri Sep 22 14:22:15 WEST 2023** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Sep 22 14:22:23 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file +This report was generated on **Sun Sep 24 16:41:42 WEST 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file From 07e697f6ad9904f317fb596d7ea14c8bbe470913 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 26 Sep 2023 15:53:03 +0100 Subject: [PATCH 11/17] Use the correct form of the verb. --- .../storage/datastore/given/DsProjectionColumnsTestEnv.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datastore/src/test/java/io/spine/server/storage/datastore/given/DsProjectionColumnsTestEnv.java b/datastore/src/test/java/io/spine/server/storage/datastore/given/DsProjectionColumnsTestEnv.java index 52f0f1d7..44244a43 100644 --- a/datastore/src/test/java/io/spine/server/storage/datastore/given/DsProjectionColumnsTestEnv.java +++ b/datastore/src/test/java/io/spine/server/storage/datastore/given/DsProjectionColumnsTestEnv.java @@ -65,7 +65,7 @@ public final class DsProjectionColumnsTestEnv { public static final Kind COLLEGE_KIND = Kind.of(TypeUrl.from(College.getDescriptor())); /** - * Prevent this test environment from instantiation. + * Prevents this test environment from instantiation. */ private DsProjectionColumnsTestEnv() { } From 91ad656ba0d5fb6ce2d230e0e66c350b3978e8c3 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 26 Sep 2023 17:37:46 +0100 Subject: [PATCH 12/17] Rename the variable. --- .../datastore/given/DsProjectionColumnsTestEnv.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/datastore/src/test/java/io/spine/server/storage/datastore/given/DsProjectionColumnsTestEnv.java b/datastore/src/test/java/io/spine/server/storage/datastore/given/DsProjectionColumnsTestEnv.java index 44244a43..ee391ca7 100644 --- a/datastore/src/test/java/io/spine/server/storage/datastore/given/DsProjectionColumnsTestEnv.java +++ b/datastore/src/test/java/io/spine/server/storage/datastore/given/DsProjectionColumnsTestEnv.java @@ -39,7 +39,6 @@ import io.spine.base.Time; import io.spine.core.Version; import io.spine.core.Versions; -import io.spine.protobuf.AnyPacker; import io.spine.server.entity.EntityRecord; import io.spine.server.entity.given.Given; import io.spine.server.entity.storage.ColumnTypeMapping; @@ -55,6 +54,7 @@ import static com.google.cloud.Timestamp.ofTimeSecondsAndNanos; import static io.spine.base.Identifier.newUuid; +import static io.spine.protobuf.AnyPacker.pack; /** * A test environment for {@link DsProjectionColumnsTest}. @@ -97,15 +97,15 @@ public static College newCollege(CollegeId id, Timestamp admissionDeadline) { .setStateSponsored(false) .setCreated(Time.currentTime()) .addAllSubjects( - ImmutableList.of("English Literature", "Engineering", "Psychology")) + ImmutableList.of("English Literature", "Engineering","Psychology")) .vBuild(); return college; } - private static EntityRecord toEntityRecord(College collegeNoAdmission, Version version) { + private static EntityRecord toEntityRecord(College college, Version version) { EntityRecord recordNoAdmission = EntityRecord .newBuilder() - .setState(AnyPacker.pack(collegeNoAdmission)) + .setState(pack(college)) .setVersion(version) .vBuild(); return recordNoAdmission; From a7dcee95aa8c6b77953c0c6b8a77f08da02116d3 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 26 Sep 2023 17:49:28 +0100 Subject: [PATCH 13/17] Comment on the warning suppression. --- .../storage/datastore/given/DsProjectionColumnsTestEnv.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datastore/src/test/java/io/spine/server/storage/datastore/given/DsProjectionColumnsTestEnv.java b/datastore/src/test/java/io/spine/server/storage/datastore/given/DsProjectionColumnsTestEnv.java index ee391ca7..1bc7b38d 100644 --- a/datastore/src/test/java/io/spine/server/storage/datastore/given/DsProjectionColumnsTestEnv.java +++ b/datastore/src/test/java/io/spine/server/storage/datastore/given/DsProjectionColumnsTestEnv.java @@ -160,7 +160,7 @@ public static final class CustomMapping extends DsColumnMapping { return ImmutableMap.of(Timestamp.class, ofNullableTimestamp()); } - @SuppressWarnings("UnnecessaryLambda") + @SuppressWarnings("UnnecessaryLambda" /* For brevity */) private static ColumnTypeMapping> ofNullableTimestamp() { return timestamp -> { if (timestamp.equals(Timestamp.getDefaultInstance())) { From ed9eab69d7e20be9f80f95ce2e6640ec6ff111fe Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 27 Sep 2023 12:18:05 +0100 Subject: [PATCH 14/17] Move the long name of warning to the next line. --- .../io/spine/server/storage/datastore/DsColumnMapping.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/datastore/src/main/java/io/spine/server/storage/datastore/DsColumnMapping.java b/datastore/src/main/java/io/spine/server/storage/datastore/DsColumnMapping.java index d62a491b..6c0dfec5 100644 --- a/datastore/src/main/java/io/spine/server/storage/datastore/DsColumnMapping.java +++ b/datastore/src/main/java/io/spine/server/storage/datastore/DsColumnMapping.java @@ -169,7 +169,8 @@ protected ColumnTypeMapping ofMessage() { /** * Returns the default mapping from {@link Timestamp} to {@link TimestampValue}. */ - @SuppressWarnings({"ProtoTimestampGetSecondsGetNano" /* In order to create exact value. */, + @SuppressWarnings({ + "ProtoTimestampGetSecondsGetNano" /* In order to create exact value. */, "UnnecessaryLambda" /* For brevity.*/, "WeakerAccess" /* To allow access for SPI users. */}) protected static ColumnTypeMapping ofTimestamp() { From 70b8108eea68c691ca667496f2eb4bba5cd5a4b2 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 27 Sep 2023 12:18:33 +0100 Subject: [PATCH 15/17] Format another warning suppression in the same manner, as above. --- .../io/spine/server/storage/datastore/DsColumnMapping.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/datastore/src/main/java/io/spine/server/storage/datastore/DsColumnMapping.java b/datastore/src/main/java/io/spine/server/storage/datastore/DsColumnMapping.java index 6c0dfec5..b76a62ca 100644 --- a/datastore/src/main/java/io/spine/server/storage/datastore/DsColumnMapping.java +++ b/datastore/src/main/java/io/spine/server/storage/datastore/DsColumnMapping.java @@ -182,7 +182,8 @@ protected static ColumnTypeMapping ofTimestamp() { /** * Returns the default mapping from {@link Version} to {@link LongValue}. */ - @SuppressWarnings({"UnnecessaryLambda" /* For brevity.*/, + @SuppressWarnings({ + "UnnecessaryLambda" /* For brevity.*/, "WeakerAccess" /* To allow access for SPI users. */}) protected static ColumnTypeMapping ofVersion() { return version -> LongValue.of(version.getNumber()); From 377c277502a352d6b1accc6dd49e11b318cd9814 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 27 Sep 2023 12:20:33 +0100 Subject: [PATCH 16/17] Shorten the `@DisplayName`. --- .../storage/datastore/DsProjectionColumnsTest.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/datastore/src/test/java/io/spine/server/storage/datastore/DsProjectionColumnsTest.java b/datastore/src/test/java/io/spine/server/storage/datastore/DsProjectionColumnsTest.java index c911effd..500fe73b 100644 --- a/datastore/src/test/java/io/spine/server/storage/datastore/DsProjectionColumnsTest.java +++ b/datastore/src/test/java/io/spine/server/storage/datastore/DsProjectionColumnsTest.java @@ -57,16 +57,15 @@ final class DsProjectionColumnsTest { private static final TestDatastoreStorageFactory datastoreFactory = local(new CustomMapping()); @Test - @DisplayName( - "allow clearing the column values if they get Proto's default values, " + - "by having a custom column mapping " + - "returning Datastore's `null` for respective values") + @DisplayName("allow clearing the column values " + + "if the column mapping used returns Datastore-specific `null`" + + "for their values") void clearTimestampColumns() { ContextSpec spec = singleTenantSpec(); ProjectionStorage storage = datastoreFactory.createProjectionStorage(spec, COLLEGE_CLS); DatastoreWrapper datastore = datastoreFactory.createDatastoreWrapper(false); - + CollegeId id = newId(); Key key = datastore.keyFor(COLLEGE_KIND, ofEntityId(id)); Version version = someVersion(); From 634e46fbf3df85daf7b22839fc40d3a8f386cab4 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 27 Sep 2023 12:20:48 +0100 Subject: [PATCH 17/17] Add a missing space symbol. --- .../spine/server/storage/datastore/DsProjectionColumnsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datastore/src/test/java/io/spine/server/storage/datastore/DsProjectionColumnsTest.java b/datastore/src/test/java/io/spine/server/storage/datastore/DsProjectionColumnsTest.java index 500fe73b..5244e618 100644 --- a/datastore/src/test/java/io/spine/server/storage/datastore/DsProjectionColumnsTest.java +++ b/datastore/src/test/java/io/spine/server/storage/datastore/DsProjectionColumnsTest.java @@ -58,7 +58,7 @@ final class DsProjectionColumnsTest { @Test @DisplayName("allow clearing the column values " + - "if the column mapping used returns Datastore-specific `null`" + + "if the column mapping used returns Datastore-specific `null` " + "for their values") void clearTimestampColumns() { ContextSpec spec = singleTenantSpec();