From af1a72ec9ad7c51f48216bc602f04e246572e5f8 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Sat, 2 Sep 2023 17:52:25 +0100 Subject: [PATCH 01/55] Bump the version -> `2.0.0-SNAPSHOT.158`. --- version.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle.kts b/version.gradle.kts index d6ace7cb82..89e1dd6a89 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -29,4 +29,4 @@ * * For versions of Spine-based dependencies, please see [io.spine.internal.dependency.Spine]. */ -val versionToPublish: String by extra("2.0.0-SNAPSHOT.157") +val versionToPublish: String by extra("2.0.0-SNAPSHOT.158") From 11690b95dd1ca4aeebb64b8b9fc15d5ed856d123 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Sat, 2 Sep 2023 18:12:35 +0100 Subject: [PATCH 02/55] Expose the definition of `AggregateEventRecord` columns. --- .../server/aggregate/AggregateEventRecordColumn.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/io/spine/server/aggregate/AggregateEventRecordColumn.java b/server/src/main/java/io/spine/server/aggregate/AggregateEventRecordColumn.java index db939bbcfc..1d5ba4e059 100644 --- a/server/src/main/java/io/spine/server/aggregate/AggregateEventRecordColumn.java +++ b/server/src/main/java/io/spine/server/aggregate/AggregateEventRecordColumn.java @@ -40,10 +40,11 @@ * Columns stored along with an {@link AggregateEventRecord}. */ @RecordColumns(ofType = AggregateEventRecord.class) -@SuppressWarnings( - {"DuplicateStringLiteralInspection", /* Column names may repeat across records. */ - "BadImport"}) /* `create` looks fine in this context. */ -final class AggregateEventRecordColumn { +@SuppressWarnings({ + "DuplicateStringLiteralInspection" /* Column names may repeat across records. */, + "BadImport" /* `create` looks fine in this context. */, + "WeakerAccess" /* This type is used in downstream Spine libraries. */}) +public final class AggregateEventRecordColumn { /** * Stores the identifier of an aggregate. From f84a7a3b2b40a7656910835387b07d8566819aee Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Sat, 2 Sep 2023 18:12:53 +0100 Subject: [PATCH 03/55] Use the latest published Spine.core version. --- buildSrc/src/main/kotlin/io/spine/internal/dependency/Spine.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/io/spine/internal/dependency/Spine.kt b/buildSrc/src/main/kotlin/io/spine/internal/dependency/Spine.kt index d1d81d50c5..48e180ec1b 100644 --- a/buildSrc/src/main/kotlin/io/spine/internal/dependency/Spine.kt +++ b/buildSrc/src/main/kotlin/io/spine/internal/dependency/Spine.kt @@ -75,7 +75,7 @@ object Spine { * @see [Spine.CoreJava.server] * @see core-java */ - const val core = "2.0.0-SNAPSHOT.155" + const val core = "2.0.0-SNAPSHOT.157" /** * The version of [Spine.modelCompiler]. From b1ae9dbdccddb060d4e6ff235e14c685464e805a Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Sat, 2 Sep 2023 18:13:01 +0100 Subject: [PATCH 04/55] Update the reports. --- license-report.md | 24 ++++++++++++------------ pom.xml | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/license-report.md b/license-report.md index fcc3813079..b515ae2fef 100644 --- a/license-report.md +++ b/license-report.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine:spine-client:2.0.0-SNAPSHOT.157` +# Dependencies of `io.spine:spine-client:2.0.0-SNAPSHOT.158` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -804,12 +804,12 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Aug 26 16:31:38 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 **Sat Sep 02 18:01:27 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:spine-core:2.0.0-SNAPSHOT.157` +# Dependencies of `io.spine:spine-core:2.0.0-SNAPSHOT.158` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -1573,12 +1573,12 @@ This report was generated on **Sat Aug 26 16:31:38 WEST 2023** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Aug 26 16:31:39 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 **Sat Sep 02 18:01:28 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:spine-server:2.0.0-SNAPSHOT.157` +# Dependencies of `io.spine:spine-server:2.0.0-SNAPSHOT.158` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -2390,12 +2390,12 @@ This report was generated on **Sat Aug 26 16:31:39 WEST 2023** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Aug 26 16:31:39 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 **Sat Sep 02 18:01:29 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.tools:spine-testutil-client:2.0.0-SNAPSHOT.157` +# Dependencies of `io.spine.tools:spine-testutil-client:2.0.0-SNAPSHOT.158` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -3327,12 +3327,12 @@ This report was generated on **Sat Aug 26 16:31:39 WEST 2023** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Aug 26 16:31:39 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 **Sat Sep 02 18:01:29 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.tools:spine-testutil-core:2.0.0-SNAPSHOT.157` +# Dependencies of `io.spine.tools:spine-testutil-core:2.0.0-SNAPSHOT.158` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -4264,12 +4264,12 @@ This report was generated on **Sat Aug 26 16:31:39 WEST 2023** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Aug 26 16:31:40 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 **Sat Sep 02 18:01:30 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.tools:spine-testutil-server:2.0.0-SNAPSHOT.157` +# Dependencies of `io.spine.tools:spine-testutil-server:2.0.0-SNAPSHOT.158` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -5249,4 +5249,4 @@ This report was generated on **Sat Aug 26 16:31:40 WEST 2023** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Aug 26 16:31:40 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 **Sat Sep 02 18:01:30 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 fad569d65b..86513cd897 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ all modules and does not describe the project structure per-subproject. --> io.spine spine-core-java -2.0.0-SNAPSHOT.157 +2.0.0-SNAPSHOT.158 2015 From 9c63b470d9bdcb28fadcd639770eef9c8e2b4ca2 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Sat, 2 Sep 2023 18:19:01 +0100 Subject: [PATCH 05/55] Expose the column definitions themselves as well. --- .../server/aggregate/AggregateEventRecordColumn.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/io/spine/server/aggregate/AggregateEventRecordColumn.java b/server/src/main/java/io/spine/server/aggregate/AggregateEventRecordColumn.java index 1d5ba4e059..26a82aa6c6 100644 --- a/server/src/main/java/io/spine/server/aggregate/AggregateEventRecordColumn.java +++ b/server/src/main/java/io/spine/server/aggregate/AggregateEventRecordColumn.java @@ -49,31 +49,31 @@ public final class AggregateEventRecordColumn { /** * Stores the identifier of an aggregate. */ - static final RecordColumn + public static final RecordColumn aggregate_id = create("aggregate_id", Any.class, AggregateEventRecord::getAggregateId); /** * Stores the time when the event record was created. */ - static final RecordColumn + public static final RecordColumn created = create("created", Timestamp.class, AggregateEventRecord::getTimestamp); /** * Stores the version of the record, either of the stored event, or the snapshot. */ - static final RecordColumn + public static final RecordColumn version = create("version", Integer.class, new GetVersion()); /** * Stores {@code true} for the records which hold snapshots, {@code false} otherwise. */ - static final RecordColumn + public static final RecordColumn snapshot = create("snapshot", Boolean.class, AggregateEventRecord::hasSnapshot); /** * Prevents this type from instantiation. * - *

This class exists exclusively as a container of the column definitions. Thus it isn't + *

This class exists exclusively as a container of the column definitions. Thus, it isn't * expected to be instantiated at all. See the {@link RecordColumns} docs for more details on * this approach. */ @@ -83,7 +83,7 @@ private AggregateEventRecordColumn() { /** * Returns all the column definitions. */ - static Columns definitions() { + public static Columns definitions() { return Columns.of(aggregate_id, created, version, snapshot); } From 5255ef67cf2caa3cd3d6e7710e85971ede70a9ce Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Sun, 3 Sep 2023 15:10:37 +0100 Subject: [PATCH 06/55] Explicitly read and close the storage iterator when reading Aggregate's history. See [jdbc-storage#155] on why this matters. --- .../main/java/io/spine/server/aggregate/AggregateStorage.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/io/spine/server/aggregate/AggregateStorage.java b/server/src/main/java/io/spine/server/aggregate/AggregateStorage.java index 8587d690da..c009e36cc8 100644 --- a/server/src/main/java/io/spine/server/aggregate/AggregateStorage.java +++ b/server/src/main/java/io/spine/server/aggregate/AggregateStorage.java @@ -417,7 +417,9 @@ Iterator historyBackward(I id, int batchSize) { */ protected Iterator historyBackward(I id, int batchSize, @Nullable Version startingFrom) { - return historyBackward.read(id, batchSize, startingFrom); + var original = historyBackward.read(id, batchSize, startingFrom); + var copied = ImmutableList.copyOf(original); + return copied.iterator(); } /** From 78fb267dea96014b0d9a234195a3dc8ba2f46f69 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 7 Sep 2023 16:52:50 +0100 Subject: [PATCH 07/55] Expose `Delivery.contextSpec()` to storage-specific Spine libraries. --- server/src/main/java/io/spine/server/delivery/Delivery.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/io/spine/server/delivery/Delivery.java b/server/src/main/java/io/spine/server/delivery/Delivery.java index 1e808d3584..0c5a2dab3b 100644 --- a/server/src/main/java/io/spine/server/delivery/Delivery.java +++ b/server/src/main/java/io/spine/server/delivery/Delivery.java @@ -794,8 +794,9 @@ protected void onShardUpdated(InboxMessage message) { * Returns the specification of a Bounded Context describing the {@code Delivery} flow. * @param multitenant whether the Bounded Context supports multi-tenancy */ - @SuppressWarnings("TestOnlyProblems") // The called code is not test-only. - static ContextSpec contextSpec(boolean multitenant) { + @SuppressWarnings({"TestOnlyProblems" /* The called code is not test-only. */, + "WeakerAccess" /* Used in descendant libraries to configure storage. */}) + public static ContextSpec contextSpec(boolean multitenant) { var name = SYSTEM_DELIVERY.value(); return multitenant ? ContextSpec.multitenant(name) : From 096cddef30c1ea250d9d46e9c273412b57eec736 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 7 Sep 2023 17:13:01 +0100 Subject: [PATCH 08/55] Document the idea of having `Delivery`-related things in a single-tenant mode. --- .../io/spine/server/delivery/CatchUpStorage.java | 13 +++++++++++++ .../java/io/spine/server/delivery/Delivery.java | 8 +++++++- .../java/io/spine/server/delivery/InboxStorage.java | 13 +++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/io/spine/server/delivery/CatchUpStorage.java b/server/src/main/java/io/spine/server/delivery/CatchUpStorage.java index 3d2c9fbf17..294c11b7cf 100644 --- a/server/src/main/java/io/spine/server/delivery/CatchUpStorage.java +++ b/server/src/main/java/io/spine/server/delivery/CatchUpStorage.java @@ -44,6 +44,19 @@ @SPI public class CatchUpStorage extends MessageStorage { + /** + * Creates a new instance of this storage. + * + *

It is recommended to have {@code CatchUpStorage} instances single-tenant only. + * It is so, because no distinction should be made for processing of {@code InboxMessage}s + * sent during the catch-up process, since it is batch-based anyway, + * and splitting batches even more (across tenants) reduces the performance. + * + * @param factory + * storage factory to create an underlying record storage + * @param multitenant + * whether {@code CatchUpStorage} should be multi-tenant + */ public CatchUpStorage(StorageFactory factory, boolean multitenant) { super(Delivery.contextSpec(multitenant), factory.createRecordStorage(Delivery.contextSpec(multitenant), getSpec())); diff --git a/server/src/main/java/io/spine/server/delivery/Delivery.java b/server/src/main/java/io/spine/server/delivery/Delivery.java index 0c5a2dab3b..67e68002a4 100644 --- a/server/src/main/java/io/spine/server/delivery/Delivery.java +++ b/server/src/main/java/io/spine/server/delivery/Delivery.java @@ -792,7 +792,13 @@ protected void onShardUpdated(InboxMessage message) { /** * Returns the specification of a Bounded Context describing the {@code Delivery} flow. - * @param multitenant whether the Bounded Context supports multi-tenancy + * + *

Usually, this context is single-tenant, as the batch processing + * of {@code InboxMessage}s is more effective when the batches are not + * split across tenants. + * + * @param multitenant + * whether the Bounded Context supports multi-tenancy */ @SuppressWarnings({"TestOnlyProblems" /* The called code is not test-only. */, "WeakerAccess" /* Used in descendant libraries to configure storage. */}) diff --git a/server/src/main/java/io/spine/server/delivery/InboxStorage.java b/server/src/main/java/io/spine/server/delivery/InboxStorage.java index 24360a3219..960f0a8c17 100644 --- a/server/src/main/java/io/spine/server/delivery/InboxStorage.java +++ b/server/src/main/java/io/spine/server/delivery/InboxStorage.java @@ -58,6 +58,19 @@ @SPI public class InboxStorage extends MessageStorage { + /** + * Creates a new instance of this storage. + * + *

Generally, {@code InboxStorage} instances should be single-tenant only. + * It is so, because no distinction should be made for processing of {@code InboxMessage}s, + * since it is batch-based anyway, and splitting batches even more (across tenants) + * reduces the performance. + * + * @param factory + * storage factory to create an underlying record storage + * @param multitenant + * whether {@code InboxStorage} should be multi-tenant + */ public InboxStorage(StorageFactory factory, boolean multitenant) { super(Delivery.contextSpec(multitenant), factory.createRecordStorage(Delivery.contextSpec(multitenant), spec())); From d7cbb243573d8afd6219128ce9dd886643bb46bf Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Sat, 30 Sep 2023 13:53:54 +0100 Subject: [PATCH 09/55] Preserve the column type when transforming a generic `Column` to a `RecordColumn`. --- .../spine/server/entity/storage/AsEntityRecordColumn.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/io/spine/server/entity/storage/AsEntityRecordColumn.java b/server/src/main/java/io/spine/server/entity/storage/AsEntityRecordColumn.java index 1dde237b0c..332e92374e 100644 --- a/server/src/main/java/io/spine/server/entity/storage/AsEntityRecordColumn.java +++ b/server/src/main/java/io/spine/server/entity/storage/AsEntityRecordColumn.java @@ -43,7 +43,7 @@ *

Such a "type cast" is required in order to adapt the all the column types available * to the framework users into the {@code RecordColumn}s, which are used in * {@linkplain io.spine.query.RecordQuery the common data structures} for - * {@linkplain io.spine.server.storage.RecordStorage storage and retrieval} of the records data. + * {@linkplain io.spine.server.storage.RecordStorage storage and retrieval} of the records' data. */ final class AsEntityRecordColumn { @@ -89,8 +89,9 @@ private AsEntityRecordColumn() { * the column to create a view for * @return a view on the column */ - static RecordColumn apply(Column original) { - return apply(original, Object.class); + static RecordColumn apply(Column original) { + var type = original.type(); + return apply(original, type); } /** From e2debdcb1a429858a92a2072d338a11752cf36d5 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Sat, 30 Sep 2023 13:54:26 +0100 Subject: [PATCH 10/55] Avoid the parameter type erasure when transforming a query to a `RecordQuery`. --- .../spine/server/entity/storage/ToEntityRecordQuery.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/io/spine/server/entity/storage/ToEntityRecordQuery.java b/server/src/main/java/io/spine/server/entity/storage/ToEntityRecordQuery.java index 302ad2c637..d22c61455b 100644 --- a/server/src/main/java/io/spine/server/entity/storage/ToEntityRecordQuery.java +++ b/server/src/main/java/io/spine/server/entity/storage/ToEntityRecordQuery.java @@ -224,11 +224,12 @@ private void copyPredicates(Subject subject) { * @return the same {@code builder} as passed, for call chaining */ @CanIgnoreReturnValue - private RecordQueryBuilder + private RecordQueryBuilder addParameter(RecordQueryBuilder builder, Column source, - ComparisonOperator operator, Object value) { - var column = AsEntityRecordColumn.apply(source); - + ComparisonOperator operator, V value) { + @SuppressWarnings("unchecked") + var castSource = (Column) source; + var column = AsEntityRecordColumn.apply(castSource); var where = builder.where(column); switch (operator) { case EQUALS: From c9b0391b7fa891257492889fea254d67b8b18723 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Sat, 30 Sep 2023 14:17:43 +0100 Subject: [PATCH 11/55] Extract `FieldMaskApplier` to a higher level, so that it could be re-used in Spine storage libraries. --- .../server/storage/FieldMaskApplier.java | 95 +++++++++++++++++++ .../server/storage/memory/TenantRecords.java | 54 +---------- 2 files changed, 98 insertions(+), 51 deletions(-) create mode 100644 server/src/main/java/io/spine/server/storage/FieldMaskApplier.java diff --git a/server/src/main/java/io/spine/server/storage/FieldMaskApplier.java b/server/src/main/java/io/spine/server/storage/FieldMaskApplier.java new file mode 100644 index 0000000000..b3b5c40c2c --- /dev/null +++ b/server/src/main/java/io/spine/server/storage/FieldMaskApplier.java @@ -0,0 +1,95 @@ +/* + * 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; + +import com.google.protobuf.Any; +import com.google.protobuf.FieldMask; +import com.google.protobuf.Message; +import io.spine.server.entity.EntityRecord; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.function.Function; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.spine.protobuf.AnyPacker.pack; +import static io.spine.protobuf.AnyPacker.unpack; +import static io.spine.server.entity.FieldMasks.applyMask; + +/** + * A {@link Function} transforming the storage record + * by applying the given {@link FieldMask} to it. + * + *

In case the passed record is an {@link EntityRecord}, it is unpacked, + * and masking is performed for the packed state. + * The resulting {@code EntityRecord} is going to have + * the same fields as the original, except its {@code state} field, + * transformed via masking. + * + *

If the passed record is a regular Proto message, + * a {@linkplain io.spine.server.entity.FieldMasks#applyMask(FieldMask, Message) + * simple masking procedure} is performed. + * + * @param + * the type of the record + */ +public final class FieldMaskApplier implements Function { + + private final FieldMask fieldMask; + + public FieldMaskApplier(FieldMask fieldMask) { + this.fieldMask = fieldMask; + } + + @SuppressWarnings("unchecked") + @Override + public @Nullable R apply(@Nullable R input) { + if (null == input || fieldMask.getPathsList() + .isEmpty()) { + return input; + } + if (input instanceof EntityRecord) { + return (R) maskEntityRecord((EntityRecord) input); + } + return applyMask(fieldMask, input); + } + + private EntityRecord maskEntityRecord(EntityRecord input) { + checkNotNull(input); + var maskedState = maskAny(input.getState()); + var result = EntityRecord.newBuilder(input) + .setState(maskedState) + .build(); + return result; + } + + private Any maskAny(Any message) { + var stateMessage = unpack(message); + var maskedMessage = applyMask(fieldMask, stateMessage); + var result = pack(maskedMessage); + return result; + } +} diff --git a/server/src/main/java/io/spine/server/storage/memory/TenantRecords.java b/server/src/main/java/io/spine/server/storage/memory/TenantRecords.java index 7c1595be13..f650fb75bb 100644 --- a/server/src/main/java/io/spine/server/storage/memory/TenantRecords.java +++ b/server/src/main/java/io/spine/server/storage/memory/TenantRecords.java @@ -27,21 +27,18 @@ package io.spine.server.storage.memory; import com.google.common.collect.Iterators; -import com.google.protobuf.Any; import com.google.protobuf.FieldMask; import com.google.protobuf.Message; import io.spine.query.RecordQuery; import io.spine.query.Subject; -import io.spine.server.entity.EntityRecord; +import io.spine.server.storage.FieldMaskApplier; import io.spine.server.storage.RecordWithColumns; -import org.checkerframework.checker.nullness.qual.Nullable; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.function.Function; import java.util.stream.Stream; import static com.google.common.base.Preconditions.checkNotNull; @@ -94,7 +91,7 @@ public void put(I id, RecordWithColumns record) { *

If there is no such a message stored, returns {@code Optional.empty()}. */ public Optional get(I id, FieldMask mask) { - return get(id).map(r -> new FieldMaskApplier(mask).apply(r.record())); + return get(id).map(r -> new FieldMaskApplier(mask).apply(r.record())); } @Override @@ -112,7 +109,7 @@ Iterator readAll(RecordQuery query) { var records = findRecords(query); return records.stream() .map(RecordWithColumns::record) - .map(new FieldMaskApplier(fieldMask)) + .map(new FieldMaskApplier<>(fieldMask)) .iterator(); } @@ -152,49 +149,4 @@ private Map> filterRecords(Subject subject) { public boolean isEmpty() { return records.isEmpty(); } - - /** - * A {@link Function} transforming the {@link EntityRecord} state by applying the given - * {@link FieldMask} to it. - * - *

The resulting {@link EntityRecord} has the same fields as the given one except - * the {@code state} field, which is masked. - */ - private class FieldMaskApplier implements Function { - - private final FieldMask fieldMask; - - private FieldMaskApplier(FieldMask fieldMask) { - this.fieldMask = fieldMask; - } - - @SuppressWarnings("unchecked") - @Override - public @Nullable R apply(@Nullable R input) { - if (null == input || fieldMask.getPathsList() - .isEmpty()) { - return input; - } - if (input instanceof EntityRecord) { - return (R) maskEntityRecord((EntityRecord) input); - } - return applyMask(fieldMask, input); - } - - private EntityRecord maskEntityRecord(EntityRecord input) { - checkNotNull(input); - var maskedState = maskAny(input.getState()); - var result = EntityRecord.newBuilder(input) - .setState(maskedState) - .build(); - return result; - } - - private Any maskAny(Any message) { - var stateMessage = unpack(message); - var maskedMessage = applyMask(fieldMask, stateMessage); - var result = pack(maskedMessage); - return result; - } - } } From fb36677f6480facce77576b4da7a324433c163a4 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Sun, 1 Oct 2023 13:52:51 +0100 Subject: [PATCH 12/55] Bump the version -> `2.0.0-SNAPSHOT.159`. --- version.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle.kts b/version.gradle.kts index 89e1dd6a89..efce2f27b9 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -29,4 +29,4 @@ * * For versions of Spine-based dependencies, please see [io.spine.internal.dependency.Spine]. */ -val versionToPublish: String by extra("2.0.0-SNAPSHOT.158") +val versionToPublish: String by extra("2.0.0-SNAPSHOT.159") From ead38b7ec6f01ba92a1847b3d30f73f4fdc0754b Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Sun, 1 Oct 2023 14:20:31 +0100 Subject: [PATCH 13/55] Update the license reports. --- license-report.md | 24 ++++++++++++------------ pom.xml | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/license-report.md b/license-report.md index ee7843c72c..249fe59925 100644 --- a/license-report.md +++ b/license-report.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine:spine-client:2.0.0-SNAPSHOT.158` +# Dependencies of `io.spine:spine-client:2.0.0-SNAPSHOT.159` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -800,12 +800,12 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Sep 21 16:26:41 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 Oct 01 14:09: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:spine-core:2.0.0-SNAPSHOT.158` +# Dependencies of `io.spine:spine-core:2.0.0-SNAPSHOT.159` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -1565,12 +1565,12 @@ This report was generated on **Thu Sep 21 16:26:41 WEST 2023** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Sep 21 16:26: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 **Sun Oct 01 14:09: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). -# Dependencies of `io.spine:spine-server:2.0.0-SNAPSHOT.158` +# Dependencies of `io.spine:spine-server:2.0.0-SNAPSHOT.159` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -2378,12 +2378,12 @@ This report was generated on **Thu Sep 21 16:26: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 **Thu Sep 21 16:26: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 **Sun Oct 01 14:09: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). -# Dependencies of `io.spine.tools:spine-testutil-client:2.0.0-SNAPSHOT.158` +# Dependencies of `io.spine.tools:spine-testutil-client:2.0.0-SNAPSHOT.159` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -3311,12 +3311,12 @@ This report was generated on **Thu Sep 21 16:26: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 **Thu Sep 21 16:26:43 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 Oct 01 14:09:55 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.tools:spine-testutil-core:2.0.0-SNAPSHOT.158` +# Dependencies of `io.spine.tools:spine-testutil-core:2.0.0-SNAPSHOT.159` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -4244,12 +4244,12 @@ This report was generated on **Thu Sep 21 16:26:43 WEST 2023** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Sep 21 16:26:43 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 Oct 01 14:09:55 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.tools:spine-testutil-server:2.0.0-SNAPSHOT.158` +# Dependencies of `io.spine.tools:spine-testutil-server:2.0.0-SNAPSHOT.159` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -5225,4 +5225,4 @@ This report was generated on **Thu Sep 21 16:26:43 WEST 2023** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Sep 21 16:26:44 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 Oct 01 14:09:56 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 62b25ba4a4..9648d144d6 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ all modules and does not describe the project structure per-subproject. --> io.spine spine-core-java -2.0.0-SNAPSHOT.158 +2.0.0-SNAPSHOT.159 2015 From 0b345440ace44aa39a361c343fa9e7698111801a Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 11 Oct 2023 18:29:56 +0100 Subject: [PATCH 14/55] Avoid passing a redundant ID value along with the record, which already has its ID with it. Address minor documentation issues as well. --- .../spine/server/aggregate/AggregateEventStorage.java | 10 ++++++++++ .../io/spine/server/aggregate/AggregateStorage.java | 4 ++-- .../io/spine/server/event/store/DefaultEventStore.java | 4 ++-- .../java/io/spine/server/tenant/TenantStorage.java | 2 +- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/io/spine/server/aggregate/AggregateEventStorage.java b/server/src/main/java/io/spine/server/aggregate/AggregateEventStorage.java index 0727e89c03..673dd6358c 100644 --- a/server/src/main/java/io/spine/server/aggregate/AggregateEventStorage.java +++ b/server/src/main/java/io/spine/server/aggregate/AggregateEventStorage.java @@ -99,4 +99,14 @@ protected boolean delete(AggregateEventRecordId id) { protected void deleteAll(Iterable ids) { super.deleteAll(ids); } + + /** + * {@inheritDoc} + * + *

This method is exposed to {@code public} for SPI users. + */ + @Override + public void write(AggregateEventRecord record) { + super.write(record); + } } diff --git a/server/src/main/java/io/spine/server/aggregate/AggregateStorage.java b/server/src/main/java/io/spine/server/aggregate/AggregateStorage.java index c009e36cc8..39cc8708fe 100644 --- a/server/src/main/java/io/spine/server/aggregate/AggregateStorage.java +++ b/server/src/main/java/io/spine/server/aggregate/AggregateStorage.java @@ -72,7 +72,7 @@ *

Storing Aggregate events

* *

Each Aggregate is an event-sourced Entity. To load an Aggregate instance, one plays all - * of the events emitted by it, eventually obtaining the last known state. While the Event Store + * the events emitted by it, eventually obtaining the last known state. While the Event Store * of a Bounded Context, to which some Aggregate belongs, stores all domain events, using it * for the sake of loading an Aggregate is inefficient in most cases. An overwhelming number of * the domain events emitted in a Bounded Context and the restrictions applied by an underlying @@ -300,7 +300,7 @@ var record = newEventRecord(aggregateId, snapshot); * the record to write */ protected void writeEventRecord(I id, AggregateEventRecord record) { - eventStorage.write(record.getId(), record); + eventStorage.write(record); } /** diff --git a/server/src/main/java/io/spine/server/event/store/DefaultEventStore.java b/server/src/main/java/io/spine/server/event/store/DefaultEventStore.java index 6de4d6e558..4bb5d1ab85 100644 --- a/server/src/main/java/io/spine/server/event/store/DefaultEventStore.java +++ b/server/src/main/java/io/spine/server/event/store/DefaultEventStore.java @@ -169,13 +169,13 @@ private Iterator find(EventStreamQuery query) { private void store(Event event) { var toStore = event.clearEnrichments(); - write(toStore.getId(), toStore); + write(toStore); } private void store(Iterable events) { var records = stream(events) .map(Event::clearEnrichments) - .map((e) -> RecordWithColumns.create(e.getId(), e, recordSpec())) + .map((e) -> RecordWithColumns.create(e, recordSpec())) .collect(toImmutableList()); writeAll(records); } diff --git a/server/src/main/java/io/spine/server/tenant/TenantStorage.java b/server/src/main/java/io/spine/server/tenant/TenantStorage.java index 0dde61f0cd..df5921c3cf 100644 --- a/server/src/main/java/io/spine/server/tenant/TenantStorage.java +++ b/server/src/main/java/io/spine/server/tenant/TenantStorage.java @@ -73,7 +73,7 @@ public final void keep(TenantId id) { var optional = read(id); if (optional.isEmpty()) { var newRecord = create(id); - write(id, newRecord); + write(newRecord); } cache(id); } From 8dbec75a50d0b61ff04f7f74745c82462acf0327 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 17 Oct 2023 17:40:23 +0100 Subject: [PATCH 15/55] Allow extending `ProjectionEndToEndTest` in descendant libraries. --- .../server/projection/e2e/ProjectionEndToEndTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/src/test/java/io/spine/server/projection/e2e/ProjectionEndToEndTest.java b/server/src/test/java/io/spine/server/projection/e2e/ProjectionEndToEndTest.java index d019c75f03..cbcbf1acf4 100644 --- a/server/src/test/java/io/spine/server/projection/e2e/ProjectionEndToEndTest.java +++ b/server/src/test/java/io/spine/server/projection/e2e/ProjectionEndToEndTest.java @@ -66,11 +66,12 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -@DisplayName("`Projection` should") -class ProjectionEndToEndTest { +@DisplayName("In end-to-end usage scenario, `Projection` should") +@SuppressWarnings("WeakerAccess" /* Open for re-using in descendant Spine libraries.*/) +public class ProjectionEndToEndTest { @AfterEach - void tearDown() { + protected void tearDown() { ServerEnvironment.instance() .reset(); } From 839deeaf8e4b3013d1a15f219f9fcc6bef26002c Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 17 Oct 2023 17:48:34 +0100 Subject: [PATCH 16/55] Update the license reports. --- license-report.md | 24 ++++++++++++------------ pom.xml | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/license-report.md b/license-report.md index 6330185004..7dbf5bed77 100644 --- a/license-report.md +++ b/license-report.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine:spine-client:2.0.0-SNAPSHOT.160` +# Dependencies of `io.spine:spine-client:2.0.0-SNAPSHOT.161` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -800,12 +800,12 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Oct 13 00:27:07 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 **Tue Oct 17 17:48:03 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:spine-core:2.0.0-SNAPSHOT.160` +# Dependencies of `io.spine:spine-core:2.0.0-SNAPSHOT.161` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -1565,12 +1565,12 @@ This report was generated on **Fri Oct 13 00:27:07 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 Oct 13 00:27:08 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 **Tue Oct 17 17:48:04 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:spine-server:2.0.0-SNAPSHOT.160` +# Dependencies of `io.spine:spine-server:2.0.0-SNAPSHOT.161` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -2378,12 +2378,12 @@ This report was generated on **Fri Oct 13 00:27:08 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 Oct 13 00:27:08 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 **Tue Oct 17 17:48:04 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.tools:spine-testutil-client:2.0.0-SNAPSHOT.160` +# Dependencies of `io.spine.tools:spine-testutil-client:2.0.0-SNAPSHOT.161` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -3311,12 +3311,12 @@ This report was generated on **Fri Oct 13 00:27:08 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 Oct 13 00:27:09 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 **Tue Oct 17 17:48:05 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.tools:spine-testutil-core:2.0.0-SNAPSHOT.160` +# Dependencies of `io.spine.tools:spine-testutil-core:2.0.0-SNAPSHOT.161` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -4244,12 +4244,12 @@ This report was generated on **Fri Oct 13 00:27:09 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 Oct 13 00:27: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 **Tue Oct 17 17:48:05 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.tools:spine-testutil-server:2.0.0-SNAPSHOT.160` +# Dependencies of `io.spine.tools:spine-testutil-server:2.0.0-SNAPSHOT.161` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -5225,4 +5225,4 @@ This report was generated on **Fri Oct 13 00:27: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 Oct 13 00:27: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). \ No newline at end of file +This report was generated on **Tue Oct 17 17:48:06 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 ef23dc312f..c044183239 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ all modules and does not describe the project structure per-subproject. --> io.spine spine-core-java -2.0.0-SNAPSHOT.160 +2.0.0-SNAPSHOT.161 2015 From 6e9af022397e5bc46cc7b5aa054477f8cef09288 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 23 Oct 2023 11:26:29 +0100 Subject: [PATCH 17/55] Update version and lifecycle flags for all entities. --- .../spine/server/entity/DefaultConverter.java | 24 +++++-------------- .../spine/server/entity/StorageConverter.java | 10 ++++---- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/server/src/main/java/io/spine/server/entity/DefaultConverter.java b/server/src/main/java/io/spine/server/entity/DefaultConverter.java index 8ba428c308..a5e13c7209 100644 --- a/server/src/main/java/io/spine/server/entity/DefaultConverter.java +++ b/server/src/main/java/io/spine/server/entity/DefaultConverter.java @@ -33,9 +33,12 @@ /** * Default implementation of {@code EntityStorageConverter} for {@code AbstractEntity}. * - * @param the type of entity IDs - * @param the type of entities - * @param the type of entity states + * @param + * the type of entity IDs + * @param + * the type of entities + * @param + * the type of entity states */ final class DefaultConverter, S extends EntityState> extends StorageConverter { @@ -59,21 +62,6 @@ public StorageConverter withFieldMask(FieldMask fieldMask) { return new DefaultConverter<>(stateType, factory, fieldMask); } - /** - * Sets lifecycle flags in the builder from the entity. - * - * @param builder - * the entity builder to update - * @param entity - * the entity which data is passed to the {@link EntityRecord} we are building - */ - @SuppressWarnings("CheckReturnValue") // calling builder - @Override - protected void updateBuilder(EntityRecord.Builder builder, E entity) { - builder.setVersion(entity.version()) - .setLifecycleFlags(entity.lifecycleFlags()); - } - /** * Injects the state into an entity. * diff --git a/server/src/main/java/io/spine/server/entity/StorageConverter.java b/server/src/main/java/io/spine/server/entity/StorageConverter.java index e95853ac25..1569664715 100644 --- a/server/src/main/java/io/spine/server/entity/StorageConverter.java +++ b/server/src/main/java/io/spine/server/entity/StorageConverter.java @@ -120,18 +120,20 @@ protected E doBackward(EntityRecord entityRecord) { } /** - * Derived classes may override to additionally tune the passed entity builder. + * Sets lifecycle flags in the builder from the entity. * - *

Default implementation does nothing. + *

Derived classes may override to additionally tune + * the passed entity builder. * * @param builder * the entity builder to update * @param entity * the entity which data is passed to the {@link EntityRecord} we are building */ - @SuppressWarnings("NoopMethodInAbstractClass") // Avoid forcing empty implementations. + @SuppressWarnings("WeakerAccess" /* To allow overriding from outside of this package. */) protected void updateBuilder(EntityRecord.Builder builder, E entity) { - // Do nothing. + builder.setVersion(entity.version()) + .setLifecycleFlags(entity.lifecycleFlags()); } /** From d3f7f2878cdc9c0d85e6e8d52ce621a017292ddf Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 27 Oct 2023 14:35:56 +0100 Subject: [PATCH 18/55] Improve on grammar in Javadocs. --- .../java/io/spine/server/integration/ConfigExchange.java | 2 +- .../server/transport/memory/InMemoryTransportFactory.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/io/spine/server/integration/ConfigExchange.java b/server/src/main/java/io/spine/server/integration/ConfigExchange.java index 10c7e8dad3..8db09fd52e 100644 --- a/server/src/main/java/io/spine/server/integration/ConfigExchange.java +++ b/server/src/main/java/io/spine/server/integration/ConfigExchange.java @@ -66,7 +66,7 @@ final class ConfigExchange extends SingleChannelExchange implements AutoCloseabl /** * Starts observing the {@link ExternalEventsWanted} messages sent by other Bounded Contexts - * and, if applicable, creates the corresponding subscriptions in the passed {@code bus}. + * and, if applicable, creates the corresponding subscriptions in passed {@code bus}. * *

After such subscriptions are created, the matching events travelling through the bus * will be transmitted to other Bounded Contexts via this exchange. diff --git a/server/src/main/java/io/spine/server/transport/memory/InMemoryTransportFactory.java b/server/src/main/java/io/spine/server/transport/memory/InMemoryTransportFactory.java index eea26c1a9e..90a365125c 100644 --- a/server/src/main/java/io/spine/server/transport/memory/InMemoryTransportFactory.java +++ b/server/src/main/java/io/spine/server/transport/memory/InMemoryTransportFactory.java @@ -40,8 +40,8 @@ /** * In-memory implementation of the {@link TransportFactory}. * - *

Publishers and subscribers must be in the same JVM. Therefore this factory usage should - * be limited to tests. + *

Publishers and subscribers must be in the same JVM. + * Therefore, this factory usage should be limited to tests. */ public class InMemoryTransportFactory implements TransportFactory { @@ -54,7 +54,7 @@ public class InMemoryTransportFactory implements TransportFactory { /** Turns {@code true} upon {@linkplain #close()} closing} the factory. */ private boolean closed; - /** Prevent direct instantiation from outside of the inheritance tree. */ + /** Prevent direct instantiation from outside the inheritance tree. */ protected InMemoryTransportFactory() { } From 05ffa691ac420467b63186fe93a1d54cd43e9a52 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 27 Oct 2023 18:08:29 +0100 Subject: [PATCH 19/55] Start removing `EntityRecordSpec` and `EntityRecordWithColumns` in favor of `MessageRecordSpec` and `RecordWithColumns`. --- .../server/aggregate/AggregateStorage.java | 8 +- .../server/entity/RecordBasedRepository.java | 3 +- .../server/entity/storage/AsEntityColumn.java | 28 +-- .../entity/storage/EntityRecordSpec.java | 99 ++++---- .../entity/storage/EntityRecordStorage.java | 51 +++- .../storage/EntityRecordWithColumns.java | 133 +++++----- .../spine/server/entity/storage/Scanner.java | 209 ++++++++-------- .../server/entity/storage/SpecScanner.java | 227 ++++++++++++++++++ .../migration/mirror/MirrorMigration.java | 6 +- .../mirror/MirrorToEntityRecord.java | 9 +- .../server/storage/MessageRecordSpec.java | 9 + .../entity/storage/EntityRecordSpecTest.java | 61 ++--- .../storage/EntityRecordWithColumnsTest.java | 50 ++-- .../server/entity/storage/ScannerTest.java | 61 ++--- .../mirror/MirrorToEntityRecordTest.java | 9 +- .../projection/ProjectionColumnTest.java | 9 +- .../given/EntityRecordStorageTestEnv.java | 26 +- 17 files changed, 629 insertions(+), 369 deletions(-) create mode 100644 server/src/main/java/io/spine/server/entity/storage/SpecScanner.java diff --git a/server/src/main/java/io/spine/server/aggregate/AggregateStorage.java b/server/src/main/java/io/spine/server/aggregate/AggregateStorage.java index 39cc8708fe..89fd52c860 100644 --- a/server/src/main/java/io/spine/server/aggregate/AggregateStorage.java +++ b/server/src/main/java/io/spine/server/aggregate/AggregateStorage.java @@ -40,10 +40,10 @@ import io.spine.server.ContextSpec; import io.spine.server.entity.EntityRecord; import io.spine.server.entity.storage.EntityRecordStorage; -import io.spine.server.entity.storage.EntityRecordWithColumns; import io.spine.server.entity.storage.ToEntityRecordQuery; import io.spine.server.storage.AbstractStorage; import io.spine.server.storage.QueryConverter; +import io.spine.server.storage.RecordWithColumns; import io.spine.server.storage.StorageFactory; import org.checkerframework.checker.nullness.qual.Nullable; @@ -368,14 +368,12 @@ private void ensureStatesQueryable() { } private Class> stateClass() { - return stateStorage.recordSpec() - .entityClass() - .stateClass(); + return stateStorage.stateClass(); } protected void writeState(Aggregate aggregate) { var record = AggregateRecords.newStateRecord(aggregate, queryingEnabled); - var result = EntityRecordWithColumns.create(aggregate, record); + var result = RecordWithColumns.create(record, stateStorage.recordSpec()); stateStorage.write(result); } diff --git a/server/src/main/java/io/spine/server/entity/RecordBasedRepository.java b/server/src/main/java/io/spine/server/entity/RecordBasedRepository.java index 12fa442d69..c8dbc71756 100644 --- a/server/src/main/java/io/spine/server/entity/RecordBasedRepository.java +++ b/server/src/main/java/io/spine/server/entity/RecordBasedRepository.java @@ -503,8 +503,7 @@ private void delete(I id, Migration migration) { RecordWithColumns toRecord(E entity) { var record = storageConverter().convert(entity); requireNonNull(record); - RecordWithColumns result = - EntityRecordWithColumns.create(entity, record); + var result = RecordWithColumns.create(record, recordStorage().recordSpec()); return result; } diff --git a/server/src/main/java/io/spine/server/entity/storage/AsEntityColumn.java b/server/src/main/java/io/spine/server/entity/storage/AsEntityColumn.java index e50284b994..98f233e82b 100644 --- a/server/src/main/java/io/spine/server/entity/storage/AsEntityColumn.java +++ b/server/src/main/java/io/spine/server/entity/storage/AsEntityColumn.java @@ -26,6 +26,7 @@ package io.spine.server.entity.storage; +import io.spine.base.EntityState; import io.spine.query.Column; import io.spine.query.ColumnName; import io.spine.server.entity.Entity; @@ -34,33 +35,28 @@ import java.util.function.Function; /** - * A type of {@link Column}s which values are calculated on top of an instance of {@code Entity}. - * - *

It serves as an adapter for the columns using another type as a source for extracting - * values, which may be obtained from an instance of {@code Entity}. In particular, it converts - * the Entity state-based columns (which originally use Protobuf {@code Message}s as a source - * for values) into the columns taking {@code Entity} instance to calculate their values. - * In this way, Entity state-based columns become uniform with other Entity instance-based columns, - * such as lifecycle columns. + * A type of {@link Column}s which values are calculated + * on top of an instance of {@code Entity} state. * *

The returning column preserves the name and the type of the wrapped original column. * *

Callee must supply a new getter to extract the value for the resulting column from - * the {@code Entity}. However, due to the limitations of Java generics, the type of + * the {@code Entity} state. However, due to the limitations of Java generics, the type of * the returning value is not checked to be the same as the original type of the wrapped column. * See the implementation note on this matter. * - * @param - * the type of {@code Entity} serving as a source for the column value + * @param + * the type of {@code Entity} state serving as a source for the column value * @implNote This class uses a raw form of the returning value for * {@link #type() Class type()} method, since it is not possible to use the type of the * wrapped column to parameterize the type of the returning value of {@code Class}. */ +// TODO:alex.tymchenko:2023-10-27: kill! @SuppressWarnings({"rawtypes", "unchecked"}) /* See the impl. note. */ -final class AsEntityColumn> implements Column { +final class AsEntityColumn> implements Column { private final Column column; - private final Function getter; + private final Function getter; /** * Creates a new instance of the adapter-column. @@ -68,9 +64,9 @@ final class AsEntityColumn> implements Column * @param column * the column to wrap * @param getter - * a getter extracting the column value from the {@code Entity} + * a getter extracting the column value from the {@code Entity} state */ - AsEntityColumn(Column column, Function getter) { + AsEntityColumn(Column column, Function getter) { this.column = column; this.getter = getter; } @@ -86,7 +82,7 @@ public Class type() { } @Override - public @Nullable Object valueIn(E source) { + public @Nullable Object valueIn(S source) { return getter.apply(source); } } diff --git a/server/src/main/java/io/spine/server/entity/storage/EntityRecordSpec.java b/server/src/main/java/io/spine/server/entity/storage/EntityRecordSpec.java index 82c914ca04..c510aa0e54 100644 --- a/server/src/main/java/io/spine/server/entity/storage/EntityRecordSpec.java +++ b/server/src/main/java/io/spine/server/entity/storage/EntityRecordSpec.java @@ -46,7 +46,6 @@ import java.util.Optional; import static com.google.common.base.Preconditions.checkNotNull; -import static io.spine.server.entity.model.EntityClass.asParameterizedEntityClass; import static java.util.Collections.unmodifiableMap; /** @@ -84,54 +83,56 @@ private EntityRecordSpec(EntityClass entityClass, EntityColumns columns) { this.columns = columns; } - /** - * Gathers columns declared by the entity class. - * - * @param entityClass - * the class of the entity - * @param - * the type of the entity identifiers - * @param - * the type of the entity - * @param - * the type of the entity state - */ - public static , E extends Entity> - EntityRecordSpec of(EntityClass entityClass) { - checkNotNull(entityClass); - var scanner = new Scanner<>(entityClass); - return new EntityRecordSpec<>(entityClass, scanner.columns()); - } - - /** - * Gathers columns declared by the class of the passed entity. - * - * @param entity - * the entity instance - * @param - * the type of the entity identifiers - * @param - * the type of the entity - * @param - * the type of the entity state - */ - public static , E extends Entity> - EntityRecordSpec of(E entity) { - checkNotNull(entity); - @SuppressWarnings("unchecked") // Ensured by the entity type declaration. - var modelClass = (EntityClass) entity.modelClass(); - var scanner = new Scanner<>(modelClass); - return new EntityRecordSpec<>(modelClass, scanner.columns()); - } - - /** - * Gathers columns of the entity class. - */ - public static , E extends Entity> - EntityRecordSpec of(Class cls) { - var aClass = asParameterizedEntityClass(cls); - return of(aClass); - } + // TODO:alex.tymchenko:2023-10-27: kill? +// /** +// * Gathers columns declared by the entity class. +// * +// * @param entityClass +// * the class of the entity +// * @param +// * the type of the entity identifiers +// * @param +// * the type of the entity +// * @param +// * the type of the entity state +// */ +// public static , E extends Entity> +// EntityRecordSpec of(EntityClass entityClass) { +// checkNotNull(entityClass); +// var scanner = new Scanner<>(entityClass); +// return new EntityRecordSpec<>(entityClass, scanner.columns()); +// } + + // TODO:alex.tymchenko:2023-10-27: kill both! +// /** +// * Gathers columns declared by the class of the passed entity. +// * +// * @param entity +// * the entity instance +// * @param +// * the type of the entity identifiers +// * @param +// * the type of the entity +// * @param +// * the type of the entity state +// */ +// public static , E extends Entity> +// EntityRecordSpec of(E entity) { +// checkNotNull(entity); +// @SuppressWarnings("unchecked") // Ensured by the entity type declaration. +// var modelClass = (EntityClass) entity.modelClass(); +// var scanner = new Scanner<>(modelClass); +// return new EntityRecordSpec<>(modelClass, scanner.columns()); +// } +// +// /** +// * Gathers columns of the entity class. +// */ +// public static , E extends Entity> +// EntityRecordSpec of(Class cls) { +// var aClass = asParameterizedEntityClass(cls); +// return of(aClass); +// } /** * Extracts column values from the entity. diff --git a/server/src/main/java/io/spine/server/entity/storage/EntityRecordStorage.java b/server/src/main/java/io/spine/server/entity/storage/EntityRecordStorage.java index 1e629531ae..2880efb03e 100644 --- a/server/src/main/java/io/spine/server/entity/storage/EntityRecordStorage.java +++ b/server/src/main/java/io/spine/server/entity/storage/EntityRecordStorage.java @@ -39,12 +39,15 @@ import io.spine.server.ContextSpec; import io.spine.server.entity.Entity; import io.spine.server.entity.EntityRecord; +import io.spine.server.storage.MessageRecordSpec; import io.spine.server.storage.RecordStorageDelegate; import io.spine.server.storage.RecordWithColumns; import io.spine.server.storage.StorageFactory; import java.util.Iterator; +import static io.spine.server.entity.model.EntityClass.asEntityClass; +import static io.spine.server.entity.model.EntityClass.stateClassOf; import static io.spine.server.entity.storage.EntityRecordColumn.archived; import static io.spine.server.entity.storage.EntityRecordColumn.deleted; import static io.spine.server.entity.storage.EntityRecordColumn.isLifecycleColumn; @@ -73,6 +76,8 @@ public class EntityRecordStorage> */ private final RecordQuery findActiveRecordsQuery; + private final Class stateClass; + /** * Creates a new instance. * @@ -80,20 +85,42 @@ public class EntityRecordStorage> * is created. It may be used by other framework libraries and SPI users to set properties * of an underlying DBMS (such as DB table names), with which this record storage interacts. * - * @param context specification of the Bounded Context in which the created storage will be used - * @param factory storage factory - * @param entityClass class of an Entity which data is stored in the created storage + * @param context + * specification of the Bounded Context in which the created storage will be used + * @param factory + * storage factory + * @param entityClass + * class of an Entity which data is stored in the created storage */ public EntityRecordStorage(ContextSpec context, StorageFactory factory, Class> entityClass) { - super(context, factory.createRecordStorage(context, spec(entityClass))); + super(context, factory.createRecordStorage(context, messageSpec(entityClass))); this.findActiveRecordsQuery = findActiveRecords(); + this.stateClass = stateClassOf(entityClass); + } + + // TODO:alex.tymchenko:2023-10-27: kill! +// private static > EntityRecordSpec +// spec(Class> entityClass) { +// return EntityRecordSpec.of(entityClass); +// } + + @SuppressWarnings("unchecked" /* Safety of casts is guaranteed by `I` and `S` boundaries. */) + private static > MessageRecordSpec + messageSpec(Class> entityClass) { + var cls = asEntityClass(entityClass); + var idClass = (Class) cls.idClass(); + var stateClass = (Class) cls.stateClass(); + var spec = SpecScanner.scan(idClass, stateClass); + return spec; } - private static > EntityRecordSpec - spec(Class> entityClass) { - return EntityRecordSpec.of(entityClass); + /** + * Returns the type of the state for the entities, which records are stored. + */ + public final Class stateClass() { + return stateClass; } /** @@ -195,7 +222,8 @@ public Iterator readAll(Iterable ids) { */ @Override public synchronized void write(I id, EntityRecord record) { - var wrapped = EntityRecordWithColumns.create(id, record); + // TODO:alex.tymchenko:2023-10-27: check that ID matches. + var wrapped = RecordWithColumns.create(record, recordSpec()); write(wrapped); } @@ -238,13 +266,12 @@ public boolean delete(I id) { @Internal @Override @SuppressWarnings("unchecked") // Guaranteed by the generic declaration of `EntityRecordSpec`. - public final EntityRecordSpec recordSpec() { - return (EntityRecordSpec) super.recordSpec(); + public final MessageRecordSpec recordSpec() { + return (MessageRecordSpec) super.recordSpec(); } - @SuppressWarnings("unchecked") // ensured by the `Entity` declaration. private Class idType() { - return (Class) recordSpec().entityClass().idClass(); + return recordSpec().idType(); } private static boolean hasNoIds(Query query) { diff --git a/server/src/main/java/io/spine/server/entity/storage/EntityRecordWithColumns.java b/server/src/main/java/io/spine/server/entity/storage/EntityRecordWithColumns.java index 4e386dd6dd..a2a7f7b367 100644 --- a/server/src/main/java/io/spine/server/entity/storage/EntityRecordWithColumns.java +++ b/server/src/main/java/io/spine/server/entity/storage/EntityRecordWithColumns.java @@ -28,15 +28,11 @@ import com.google.common.annotations.VisibleForTesting; import io.spine.annotation.SPI; -import io.spine.base.EntityState; import io.spine.base.Identifier; -import io.spine.protobuf.AnyPacker; import io.spine.query.ColumnName; -import io.spine.server.entity.Entity; import io.spine.server.entity.EntityRecord; import io.spine.server.entity.LifecycleFlags; import io.spine.server.entity.WithLifecycle; -import io.spine.server.entity.model.EntityClass; import io.spine.server.storage.RecordWithColumns; import org.checkerframework.checker.nullness.qual.Nullable; @@ -62,28 +58,25 @@ private EntityRecordWithColumns(I id, EntityRecord record, Map - * the type of the entity identifiers - * @param - * the type of the entity - * @return a new instance of {@code EntityRecordWithColumns} - */ - public static , E extends Entity> - EntityRecordWithColumns create(E entity, EntityRecord record) { - checkNotNull(entity); - checkNotNull(record); - var recordSpec = EntityRecordSpec.of(entity); - var storageFields = recordSpec.valuesIn(entity); - return new EntityRecordWithColumns<>(entity.id(), record, storageFields); - } + // TODO:alex.tymchenko:2023-10-27: kill! +// /** +// * Creates the new instance of {@code EntityRecordWithColumns} by evaluating the values +// * of the passed columns for the passed entity. +// * +// * @param record +// * the record prepared for storage +// * @param +// * the type of the entity identifiers +// * @return a new instance of {@code EntityRecordWithColumns} +// */ +// public static , E extends Entity> +// EntityRecordWithColumns create(E entity, EntityRecord record) { +// checkNotNull(entity); +// checkNotNull(record); +// var recordSpec = EntityRecordSpec.of(entity); +// var storageFields = recordSpec.valuesIn(entity); +// return new EntityRecordWithColumns<>(entity.id(), record, storageFields); +// } /** * Creates the new instance of {@code EntityRecordWithColumns} using the pre-created @@ -107,49 +100,51 @@ public static EntityRecordWithColumns create(I id, EntityRecord record) { return new EntityRecordWithColumns<>(id, record, lifecycleValues); } - /** - * Creates a new instance of {@code EntityRecordWithColumns} using the pre-created - * entity record and the entity class. - * - *

This method considers both lifecycle and state-based columns. - * - *

It is a responsibility of the caller to provide a record with the matching - * identifier and state types. - * - * @param record - * the record prepared for storage - * @param entityClass - * the class of entity which is stored as the given {@code EntityRecord} - * @param - * the type of the entity's identifier - * @param - * the type of the entity's state - * @param - * the type of the entity - */ - @SuppressWarnings("unchecked") // see the docs. - public static , E extends Entity> EntityRecordWithColumns - create(EntityRecord record, Class entityClass) { - checkNotNull(record); - checkNotNull(entityClass); - - var stateValues = stateColumnsFrom(record, entityClass); - var lifecycleValues = EntityRecordColumn.valuesIn(record); - var allColumnValues = merge(stateValues, lifecycleValues); - var result = (EntityRecordWithColumns) of(record, allColumnValues); - return result; - } - - @SuppressWarnings("unchecked") /* See the docs for `create(record, entityClass)`. */ - private static , E extends Entity> - Map stateColumnsFrom(EntityRecord record, Class entityClass) { - var entityClazz = EntityClass.asParameterizedEntityClass(entityClass); - var columnsScanner = new Scanner<>(entityClazz); - var entityState = (S) AnyPacker.unpack(record.getState()); - - var stateValues = columnsScanner.stateColumns().valuesIn(entityState); - return stateValues; - } +// /** +// * Creates a new instance of {@code EntityRecordWithColumns} using the pre-created +// * entity record and the entity class. +// * +// *

This method considers both lifecycle and state-based columns. +// * +// *

It is a responsibility of the caller to provide a record with the matching +// * identifier and state types. +// * +// * @param record +// * the record prepared for storage +// * @param entityClass +// * the class of entity which is stored as the given {@code EntityRecord} +// * @param +// * the type of the entity's identifier +// * @param +// * the type of the entity's state +// * @param +// * the type of the entity +// */ +// @SuppressWarnings("unchecked") // see the docs. +// public static , E extends Entity> EntityRecordWithColumns +// create(EntityRecord record, Class entityClass) { +// checkNotNull(record); +// checkNotNull(entityClass); +// +// var stateValues = stateColumnsFrom(record, entityClass); +// var lifecycleValues = EntityRecordColumn.valuesIn(record); +// var allColumnValues = merge(stateValues, lifecycleValues); +// var result = (EntityRecordWithColumns) of(record, allColumnValues); +// return result; +// } + + // TODO:alex.tymchenko:2023-10-27: kill? +// @SuppressWarnings("unchecked") /* See the docs for `create(record, entityClass)`. */ +// private static , E extends Entity> +// Map stateColumnsFrom(EntityRecord record, Class entityClass) { +// // TODO:alex.tymchenko:2023-10-27: reuse this expression? +// var entityClazz = EntityClass.asParameterizedEntityClass(entityClass); +// var columnsScanner = new Scanner<>(entityClazz); +// var entityState = (S) AnyPacker.unpack(record.getState()); +// +// var stateValues = columnsScanner.stateColumns().valuesIn(entityState); +// return stateValues; +// } private static Map merge(Map stateValues, Map lifecycleValues) { diff --git a/server/src/main/java/io/spine/server/entity/storage/Scanner.java b/server/src/main/java/io/spine/server/entity/storage/Scanner.java index a0b7049f04..663843d53b 100644 --- a/server/src/main/java/io/spine/server/entity/storage/Scanner.java +++ b/server/src/main/java/io/spine/server/entity/storage/Scanner.java @@ -28,19 +28,9 @@ import io.spine.base.EntityState; import io.spine.client.ArchivedColumn; -import io.spine.client.DeletedColumn; -import io.spine.client.VersionColumn; import io.spine.query.Column; import io.spine.query.EntityColumn; import io.spine.server.entity.Entity; -import io.spine.server.entity.model.EntityClass; -import org.checkerframework.checker.nullness.qual.Nullable; - -import java.util.HashSet; -import java.util.Set; -import java.util.function.Function; - -import static io.spine.util.Exceptions.newIllegalStateException; /** * Scans and extracts the definitions of {@link Column}s to be stored @@ -67,103 +57,104 @@ */ final class Scanner, E extends Entity> { - /** - * The name of the nested class generated by the Spine compiler as a container of - * the entity column definitions. - */ - @SuppressWarnings("DuplicateStringLiteralInspection") // coincidental duplication - private static final String COLS_NESTED_CLASSNAME = "Column"; - - /** - * The name of the method inside the column container class generated by the Spine compiler. - * - *

The method returns all the definitions of the columns for this state class. - */ - private static final String COL_DEFS_METHOD_NAME = "definitions"; - - /** - * The target entity class to scan. - */ - private final EntityClass entityClass; - - /** - * Creates an instance of scanner for a given type of {@code Entity}. - */ - Scanner(EntityClass entityClass) { - this.entityClass = entityClass; - } - - /** - * Returns all columns for the scanned {@code Entity}. - * - *

The result includes both lifecycle columns and the columns declared - * in the {@code Entity} state. - */ - EntityColumns columns() { - Set> accumulator = new HashSet<>(); - - var stateColumns = stateColumns(); - for (var stateCol : stateColumns) { - Column wrapped = wrap(stateCol, (entity) -> stateCol.valueIn(entity.state())); - accumulator.add(wrapped); - } - - accumulator.add(wrap(ArchivedColumn.instance(), Entity::isArchived)); - accumulator.add(wrap(DeletedColumn.instance(), Entity::isDeleted)); - accumulator.add(wrap(VersionColumn.instance(), Entity::version)); - - var columns = new EntityColumns<>(accumulator); - return columns; - } - - private AsEntityColumn wrap(Column origin, Function getter) { - return new AsEntityColumn<>(origin, getter); - } - - /** - * Obtains the {@linkplain EntityColumn entity-state-based} columns of the class. - */ - @SuppressWarnings("OverlyBroadCatchBlock") /* Treating all exceptions equally. */ - StateColumns stateColumns() { - var stateClass = entityClass.stateClass(); - var columnClass = findColumnsClass(stateClass); - if (columnClass == null) { - return StateColumns.none(); - } - try { - var getDefinitions = columnClass.getDeclaredMethod(COL_DEFS_METHOD_NAME); - @SuppressWarnings("unchecked") // ensured by the Spine code generation. - var columns = (Set>) getDefinitions.invoke(null); - return new StateColumns<>(columns); - } catch (Exception e) { - throw newIllegalStateException( - e, - "Error fetching the declared columns by invoking the `%s.%s()` method" + - " of the entity state type `%s`.", - COLS_NESTED_CLASSNAME, COL_DEFS_METHOD_NAME, stateClass.getName()); - } - } - - /** - * Finds the {@code Column} class which is generated the messages representing the entity - * state type. - * - *

If an entity has no such class generated, it does not declare any columns. In this - * case, this method returns {@code null}. - * - * @param stateClass - * the class of the entity state to look for the method in - * @return the class declaring the entity columns, - * or {@code null} if the entity declares no columns - */ - private static @Nullable Class findColumnsClass(Class> stateClass) { - var innerClasses = stateClass.getDeclaredClasses(); - Class columnClass = null; - for (var aClass : innerClasses) { - if (COLS_NESTED_CLASSNAME.equals(aClass.getSimpleName())) { - columnClass = aClass; - } - } - return columnClass; - } + // TODO:alex.tymchenko:2023-10-27: kill and use `SpecScanner` under this name. +// /** +// * The name of the nested class generated by the Spine compiler as a container of +// * the entity column definitions. +// */ +// @SuppressWarnings("DuplicateStringLiteralInspection") // coincidental duplication +// private static final String COLS_NESTED_CLASSNAME = "Column"; +// +// /** +// * The name of the method inside the column container class generated by the Spine compiler. +// * +// *

The method returns all the definitions of the columns for this state class. +// */ +// private static final String COL_DEFS_METHOD_NAME = "definitions"; +// +// /** +// * The target entity class to scan. +// */ +// private final EntityClass entityClass; +// +// /** +// * Creates an instance of scanner for a given type of {@code Entity}. +// */ +// Scanner(EntityClass entityClass) { +// this.entityClass = entityClass; +// } +// +// /** +// * Returns all columns for the scanned {@code Entity}. +// * +// *

The result includes both lifecycle columns and the columns declared +// * in the {@code Entity} state. +// */ +// EntityColumns columns() { +// Set> accumulator = new HashSet<>(); +// +// var stateColumns = stateColumns(); +// for (var stateCol : stateColumns) { +// Column wrapped = wrap(stateCol, (entity) -> stateCol.valueIn(entity.state())); +// accumulator.add(wrapped); +// } +// +// accumulator.add(wrap(ArchivedColumn.instance(), Entity::isArchived)); +// accumulator.add(wrap(DeletedColumn.instance(), Entity::isDeleted)); +// accumulator.add(wrap(VersionColumn.instance(), Entity::version)); +// +// var columns = new EntityColumns<>(accumulator); +// return columns; +// } +// +// private AsEntityColumn wrap(Column origin, Function getter) { +// return new AsEntityColumn<>(origin, getter); +// } +// +// /** +// * Obtains the {@linkplain EntityColumn entity-state-based} columns of the class. +// */ +// @SuppressWarnings("OverlyBroadCatchBlock") /* Treating all exceptions equally. */ +// StateColumns stateColumns() { +// var stateClass = entityClass.stateClass(); +// var columnClass = findColumnsClass(stateClass); +// if (columnClass == null) { +// return StateColumns.none(); +// } +// try { +// var getDefinitions = columnClass.getDeclaredMethod(COL_DEFS_METHOD_NAME); +// @SuppressWarnings("unchecked") // ensured by the Spine code generation. +// var columns = (Set>) getDefinitions.invoke(null); +// return new StateColumns<>(columns); +// } catch (Exception e) { +// throw newIllegalStateException( +// e, +// "Error fetching the declared columns by invoking the `%s.%s()` method" + +// " of the entity state type `%s`.", +// COLS_NESTED_CLASSNAME, COL_DEFS_METHOD_NAME, stateClass.getName()); +// } +// } +// +// /** +// * Finds the {@code Column} class which is generated the messages representing the entity +// * state type. +// * +// *

If an entity has no such class generated, it does not declare any columns. In this +// * case, this method returns {@code null}. +// * +// * @param stateClass +// * the class of the entity state to look for the method in +// * @return the class declaring the entity columns, +// * or {@code null} if the entity declares no columns +// */ +// private static @Nullable Class findColumnsClass(Class> stateClass) { +// var innerClasses = stateClass.getDeclaredClasses(); +// Class columnClass = null; +// for (var aClass : innerClasses) { +// if (COLS_NESTED_CLASSNAME.equals(aClass.getSimpleName())) { +// columnClass = aClass; +// } +// } +// return columnClass; +// } } diff --git a/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java b/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java new file mode 100644 index 0000000000..92e08e1e93 --- /dev/null +++ b/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java @@ -0,0 +1,227 @@ +/* + * 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.entity.storage; + +import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.protobuf.Any; +import io.spine.annotation.Internal; +import io.spine.base.EntityState; +import io.spine.base.Identifier; +import io.spine.client.ArchivedColumn; +import io.spine.client.DeletedColumn; +import io.spine.client.VersionColumn; +import io.spine.protobuf.AnyPacker; +import io.spine.query.Column; +import io.spine.query.Column.Getter; +import io.spine.query.EntityColumn; +import io.spine.query.RecordColumn; +import io.spine.server.entity.Entity; +import io.spine.server.entity.EntityRecord; +import io.spine.server.entity.WithLifecycle; +import io.spine.server.entity.model.EntityClass; +import io.spine.server.storage.MessageRecordSpec; +import io.spine.server.storage.MessageRecordSpec.ExtractId; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.spine.protobuf.AnyPacker.unpack; +import static io.spine.util.Exceptions.newIllegalStateException; +import static java.util.Objects.requireNonNull; + +/** + * Scans Proto definitions of stored {@code Message}s and determines + * the record specification. + */ +@Internal +public final class SpecScanner { + + /** + * The name of the nested class generated by the Spine compiler as a container of + * the entity column definitions. + */ + @SuppressWarnings("DuplicateStringLiteralInspection") // coincidental duplication + private static final String COLS_NESTED_CLASSNAME = "Column"; + + /** + * The name of the method inside the column container class generated by the Spine compiler. + * + *

The method returns all the definitions of the columns for this state class. + */ + private static final String COL_DEFS_METHOD_NAME = "definitions"; + + /** + * Prevent this utility from direct instantiation. + */ + private SpecScanner() { + } + + static > MessageRecordSpec + scan(Class idClass, Class stateClass) { + Set> accumulator = new HashSet<>(); + + var unpacker = new MemoizingUnpacker<>(stateClass); + var stateColumns = stateColumns(stateClass); + for (var stateCol : stateColumns) { + var columnName = stateCol.name(); + var columnType = castObject(stateCol); + var recordColumn = new RecordColumn<>(columnName, columnType, + getter(stateCol, unpacker)); + accumulator.add(recordColumn); + } + + accumulator.add(wrap(ArchivedColumn.instance(), WithLifecycle::isArchived)); + accumulator.add(wrap(DeletedColumn.instance(), WithLifecycle::isDeleted)); + accumulator.add(wrap(VersionColumn.instance(), EntityRecord::getVersion)); + + var result = new MessageRecordSpec<>( + idClass, EntityRecord.class, idFromRecord(), ImmutableSet.copyOf(accumulator) + ); + return result; + } + + @SuppressWarnings("unchecked" /* Casts are ensured by `Entity` declaration. */) + public static > + MessageRecordSpec scan(Entity entity) { + checkNotNull(entity); + var entityCls = (Class>) entity.getClass(); + return scan(entityCls); + } + + public static > + MessageRecordSpec scan(Class> cls) { + checkNotNull(cls); + var idClass = EntityClass.idClass(cls); + var stateClass = EntityClass.stateClassOf(cls); + return scan(idClass, stateClass); + } + + @SuppressWarnings("unchecked") + private static > + Class castObject(Column stateCol) { + return (Class) stateCol.type(); + } + + private static final class MemoizingUnpacker> { + + private final Class stateCls; + + private final Map cache = new HashMap<>(); + + private MemoizingUnpacker(Class cls) { + stateCls = cls; + } + + private synchronized S unpack(Any value) { + @Nullable S alreadyUnpacked = cache.get(value); + if (alreadyUnpacked != null) { + return alreadyUnpacked; + } + var state = AnyPacker.unpack(value, stateCls); + cache.put(value, state); + return state; + } + } + + @NonNull + private static > + Getter getter(Column stateColumn, MemoizingUnpacker unpacker) { + return r -> requireNonNull(stateColumn.valueIn(unpacker.unpack(r.getState()))); + } + + @NonNull + private static ExtractId idFromRecord() { + return new ExtractId<>() { + @SuppressWarnings("unchecked") + @CanIgnoreReturnValue + @Override + public I apply(@Nullable EntityRecord input) { + requireNonNull(input); + return (I) Identifier.unpack(input.getEntityId()); + } + }; + } + + private static RecordColumn + wrap(Column origin, Function getter) { + var result = new RecordColumn<>(origin.name(), origin.type(), getter::apply); + return result; + } + + /** + * Obtains the {@linkplain EntityColumn entity-state-based} columns of the class. + */ + @SuppressWarnings("OverlyBroadCatchBlock") /* Treating all exceptions equally. */ + private static > Set> stateColumns(Class stateClass) { + var columnClass = findColumnsClass(stateClass); + if (columnClass == null) { + return ImmutableSet.of(); + } + try { + var getDefinitions = columnClass.getDeclaredMethod(COL_DEFS_METHOD_NAME); + @SuppressWarnings("unchecked") // ensured by the Spine code generation. + var columns = (Set>) getDefinitions.invoke(null); + return ImmutableSet.copyOf(columns); + } catch (Exception e) { + throw newIllegalStateException( + e, + "Error fetching the declared columns by invoking the `%s.%s()` method" + + " of the entity state type `%s`.", + COLS_NESTED_CLASSNAME, COL_DEFS_METHOD_NAME, stateClass.getName()); + } + } + + /** + * Finds the {@code Column} class which is generated the messages representing the entity + * state type. + * + *

If an entity has no such class generated, it does not declare any columns. In this + * case, this method returns {@code null}. + * + * @param stateClass + * the class of the entity state to look for the method in + * @return the class declaring the entity columns, + * or {@code null} if the entity declares no columns + */ + private static @Nullable Class findColumnsClass(Class stateClass) { + var innerClasses = stateClass.getDeclaredClasses(); + Class columnClass = null; + for (var aClass : innerClasses) { + if (COLS_NESTED_CLASSNAME.equals(aClass.getSimpleName())) { + columnClass = aClass; + } + } + return columnClass; + } +} diff --git a/server/src/main/java/io/spine/server/migration/mirror/MirrorMigration.java b/server/src/main/java/io/spine/server/migration/mirror/MirrorMigration.java index b62033f48f..a685cc7f41 100644 --- a/server/src/main/java/io/spine/server/migration/mirror/MirrorMigration.java +++ b/server/src/main/java/io/spine/server/migration/mirror/MirrorMigration.java @@ -31,8 +31,10 @@ import io.spine.server.ContextSpec; import io.spine.server.aggregate.Aggregate; import io.spine.server.aggregate.model.AggregateClass; +import io.spine.server.entity.EntityRecord; import io.spine.server.entity.storage.EntityRecordStorage; import io.spine.server.entity.storage.EntityRecordWithColumns; +import io.spine.server.storage.RecordWithColumns; import io.spine.server.storage.StorageFactory; import io.spine.system.server.Mirror; @@ -194,7 +196,7 @@ EntityRecordStorage destinationStorage() { } private class MigrationBatch { - private final Collection> entityRecords; + private final Collection> entityRecords; private final Collection migratedMirrors; private MigrationBatch(int expectedSize) { @@ -218,7 +220,7 @@ private void transform(Iterator mirrors) { }); } - private Collection> entityRecords() { + private Collection> entityRecords() { return entityRecords; } diff --git a/server/src/main/java/io/spine/server/migration/mirror/MirrorToEntityRecord.java b/server/src/main/java/io/spine/server/migration/mirror/MirrorToEntityRecord.java index a74e10b14f..709c93d66a 100644 --- a/server/src/main/java/io/spine/server/migration/mirror/MirrorToEntityRecord.java +++ b/server/src/main/java/io/spine/server/migration/mirror/MirrorToEntityRecord.java @@ -30,6 +30,8 @@ import io.spine.server.aggregate.Aggregate; import io.spine.server.entity.EntityRecord; import io.spine.server.entity.storage.EntityRecordWithColumns; +import io.spine.server.entity.storage.SpecScanner; +import io.spine.server.storage.RecordWithColumns; import io.spine.system.server.Mirror; import javax.annotation.concurrent.Immutable; @@ -56,7 +58,7 @@ */ @Immutable final class MirrorToEntityRecord, A extends Aggregate> - implements Function> { + implements Function> { private final Class aggregateClass; @@ -76,14 +78,15 @@ final class MirrorToEntityRecord, A extends Aggregat * its identifier and/or state types differ from the ones, declared for the aggregate. */ @Override - public EntityRecordWithColumns apply(Mirror mirror) { + public RecordWithColumns apply(Mirror mirror) { var record = EntityRecord.newBuilder() .setEntityId(mirror.getId().getValue()) .setState(mirror.getState()) .setVersion(mirror.getVersion()) .setLifecycleFlags(mirror.getLifecycle()) .build(); - var recordWithColumns = EntityRecordWithColumns.create(record, aggregateClass); + var spec = SpecScanner.scan(aggregateClass); + var recordWithColumns = RecordWithColumns.create(record, spec); return recordWithColumns; } } diff --git a/server/src/main/java/io/spine/server/storage/MessageRecordSpec.java b/server/src/main/java/io/spine/server/storage/MessageRecordSpec.java index 9a11730762..49c3dd15e9 100644 --- a/server/src/main/java/io/spine/server/storage/MessageRecordSpec.java +++ b/server/src/main/java/io/spine/server/storage/MessageRecordSpec.java @@ -26,6 +26,7 @@ package io.spine.server.storage; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -152,6 +153,14 @@ public I idFromRecord(R record) { return ImmutableSet.copyOf(columns.values()); } + /** + * Returns the total number of columns in this specification. + */ + @VisibleForTesting + public int columnCount() { + return columns.size(); + } + @Override public Class sourceType() { return storedType(); diff --git a/server/src/test/java/io/spine/server/entity/storage/EntityRecordSpecTest.java b/server/src/test/java/io/spine/server/entity/storage/EntityRecordSpecTest.java index abc6774c4c..75a6d37dab 100644 --- a/server/src/test/java/io/spine/server/entity/storage/EntityRecordSpecTest.java +++ b/server/src/test/java/io/spine/server/entity/storage/EntityRecordSpecTest.java @@ -38,7 +38,6 @@ import io.spine.query.ColumnName; import io.spine.query.EntityColumn; import io.spine.server.entity.EntityRecord; -import io.spine.server.entity.storage.given.TaskListViewProjection; import io.spine.server.entity.storage.given.TaskViewProjection; import io.spine.test.entity.TaskView; import io.spine.test.entity.TaskViewId; @@ -67,15 +66,16 @@ void passNullToleranceCheck() { .testAllPublicInstanceMethods(spec()); } - @Test - @DisplayName("be extracted from an entity class") - void beExtractedFromEntityClass() { - EntityRecordSpec spec = EntityRecordSpec.of(TaskListViewProjection.class); - var columnName = ColumnName.of("description"); - var descriptionColumn = spec.findColumn(columnName); - - assertThat(descriptionColumn).isPresent(); - } + // TODO:alex.tymchenko:2023-10-27: rewrite! +// @Test +// @DisplayName("be extracted from an entity class") +// void beExtractedFromEntityClass() { +// EntityRecordSpec spec = EntityRecordSpec.of(TaskListViewProjection.class); +// var columnName = ColumnName.of("description"); +// var descriptionColumn = spec.findColumn(columnName); +// +// assertThat(descriptionColumn).isPresent(); +// } @Test @DisplayName("obtain a column by name") @@ -155,26 +155,27 @@ void returnColumns() { assertThat(spec().columnCount()).isEqualTo(expectedSize); } - @Test - @DisplayName("extract values from entity") - void extractColumnValues() { - var projection = new TaskViewProjection(); - var values = spec().valuesIn(projection); - - assertThat(values).containsExactly( - ColumnName.of("archived"), projection.isArchived(), - ColumnName.of("deleted"), projection.isDeleted(), - ColumnName.of("version"), projection.version(), - ColumnName.of("name"), projection.state() - .getName(), - ColumnName.of("estimate_in_days"), projection.state() - .getEstimateInDays(), - ColumnName.of("status"), projection.state() - .getStatus(), - ColumnName.of("due_date"), projection.state() - .getDueDate() - ); - } + // TODO:alex.tymchenko:2023-10-27: rewrite or kill? +// @Test +// @DisplayName("extract values from entity") +// void extractColumnValues() { +// var projection = new TaskViewProjection(); +// var values = spec().valuesIn(projection); +// +// assertThat(values).containsExactly( +// ColumnName.of("archived"), projection.isArchived(), +// ColumnName.of("deleted"), projection.isDeleted(), +// ColumnName.of("version"), projection.version(), +// ColumnName.of("name"), projection.state() +// .getName(), +// ColumnName.of("estimate_in_days"), projection.state() +// .getEstimateInDays(), +// ColumnName.of("status"), projection.state() +// .getStatus(), +// ColumnName.of("due_date"), projection.state() +// .getDueDate() +// ); +// } @Test @DisplayName("extract ID value from `EntityRecord`") diff --git a/server/src/test/java/io/spine/server/entity/storage/EntityRecordWithColumnsTest.java b/server/src/test/java/io/spine/server/entity/storage/EntityRecordWithColumnsTest.java index 9871141a58..47b1201c6c 100644 --- a/server/src/test/java/io/spine/server/entity/storage/EntityRecordWithColumnsTest.java +++ b/server/src/test/java/io/spine/server/entity/storage/EntityRecordWithColumnsTest.java @@ -37,8 +37,8 @@ import io.spine.query.ColumnName; import io.spine.query.RecordColumn; import io.spine.server.entity.EntityRecord; -import io.spine.server.entity.storage.given.EntityWithoutCustomColumns; import io.spine.server.entity.storage.given.TestEntity; +import io.spine.server.storage.RecordWithColumns; import io.spine.server.storage.given.TestColumnMapping; import io.spine.test.storage.StgProject; import io.spine.test.storage.StgProjectId; @@ -137,18 +137,19 @@ var record = sampleRecordWithEmptyColumns(); assertThrows(IllegalStateException.class, () -> record.columnValue(nonExistentName)); } - @Test - @DisplayName("have `Entity` lifecycle columns even if the entity does not define custom ones") - void supportEmptyColumns() { - var entity = new EntityWithoutCustomColumns(TASK_ID); - - var columns = EntityRecordSpec.of(entity); - var storageFields = columns.valuesIn(entity); - - var record = EntityRecordWithColumns.of(sampleEntityRecord(), storageFields); - assertThat(record.columnNames()) - .containsExactlyElementsIn(EntityRecordColumn.names()); - } + // TODO:alex.tymchenko:2023-10-27: kill? +// @Test +// @DisplayName("have `Entity` lifecycle columns even if the entity does not define custom ones") +// void supportEmptyColumns() { +// var entity = new EntityWithoutCustomColumns(TASK_ID); +// +// var columns = EntityRecordSpec.of(entity); +// var storageFields = columns.valuesIn(entity); +// +// var record = EntityRecordWithColumns.of(sampleEntityRecord(), storageFields); +// assertThat(record.columnNames()) +// .containsExactlyElementsIn(EntityRecordColumn.names()); +// } @Test @DisplayName("return column value by column name") @@ -191,23 +192,24 @@ var record = EntityRecordWithColumns.of(sampleEntityRecord(), storageFields); @DisplayName("support being initialized with") class BeInitializedWith { - @Test - @DisplayName("an `Entity` identifier and a record") - void idAndRecord() { - var rawRecord = sampleEntityRecord(); - Long id = 199L; - var result = EntityRecordWithColumns.create(id, rawRecord); - assertThat(result).isNotNull(); - assertThat(result.id()).isEqualTo(id); - assertThat(result.record()).isEqualTo(rawRecord); - } + // TODO:alex.tymchenko:2023-10-27: kill! +// @Test +// @DisplayName("an `Entity` identifier and a record") +// void idAndRecord() { +// var rawRecord = sampleEntityRecord(); +// Long id = 199L; +// var result = EntityRecordWithColumns.create(id, rawRecord); +// assertThat(result).isNotNull(); +// assertThat(result.id()).isEqualTo(id); +// assertThat(result.record()).isEqualTo(rawRecord); +// } @Test @DisplayName("an `Entity` and a record") void entityAndRecord() { var entity = new TestEntity(PROJECT_ID); var rawRecord = sampleEntityRecord(); - var result = EntityRecordWithColumns.create(entity, rawRecord); + var result = RecordWithColumns.create(rawRecord, SpecScanner.scan(entity)); assertThat(result).isNotNull(); assertThat(result.id()).isEqualTo(PROJECT_ID); assertThat(result.record()).isEqualTo(rawRecord); diff --git a/server/src/test/java/io/spine/server/entity/storage/ScannerTest.java b/server/src/test/java/io/spine/server/entity/storage/ScannerTest.java index 16001ffa5b..7e5b9aebca 100644 --- a/server/src/test/java/io/spine/server/entity/storage/ScannerTest.java +++ b/server/src/test/java/io/spine/server/entity/storage/ScannerTest.java @@ -44,34 +44,35 @@ @DisplayName("`Scanner` should") class ScannerTest { - @Test - @DisplayName("include `Entity` lifecycle columns into the scan results") - void extractSystemColumns() { - var entityClass = asEntityClass(TaskViewProjection.class); - var scanner = new Scanner<>(entityClass); - var columns = scanner.columns(); - - var names = columns.stream() - .map(Column::name) - .collect(toImmutableSet()); - - assertThat(names).containsAtLeastElementsIn( - ImmutableSet.of( - ArchivedColumn.instance().name(), - DeletedColumn.instance().name(), - VersionColumn.instance().name() - ) - ); - } - - @Test - @DisplayName("extract the columns declared with `(column)` option in the Protobuf message") - void extractSimpleColumns() { - var entityClass = asEntityClass(TaskListViewProjection.class); - var scanner = new Scanner<>(entityClass); - - var columns = scanner.stateColumns(); - - assertContains(columns, "description"); - } + // TODO:alex.tymchenko:2023-10-27: adapt to `SpecScanner`? +// @Test +// @DisplayName("include `Entity` lifecycle columns into the scan results") +// void extractSystemColumns() { +// var entityClass = asEntityClass(TaskViewProjection.class); +// var scanner = new Scanner<>(entityClass); +// var columns = scanner.columns(); +// +// var names = columns.stream() +// .map(Column::name) +// .collect(toImmutableSet()); +// +// assertThat(names).containsAtLeastElementsIn( +// ImmutableSet.of( +// ArchivedColumn.instance().name(), +// DeletedColumn.instance().name(), +// VersionColumn.instance().name() +// ) +// ); +// } +// +// @Test +// @DisplayName("extract the columns declared with `(column)` option in the Protobuf message") +// void extractSimpleColumns() { +// var entityClass = asEntityClass(TaskListViewProjection.class); +// var scanner = new Scanner<>(entityClass); +// +// var columns = scanner.stateColumns(); +// +// assertContains(columns, "description"); +// } } diff --git a/server/src/test/java/io/spine/server/migration/mirror/MirrorToEntityRecordTest.java b/server/src/test/java/io/spine/server/migration/mirror/MirrorToEntityRecordTest.java index 2121875bd4..d64abf536d 100644 --- a/server/src/test/java/io/spine/server/migration/mirror/MirrorToEntityRecordTest.java +++ b/server/src/test/java/io/spine/server/migration/mirror/MirrorToEntityRecordTest.java @@ -26,9 +26,10 @@ package io.spine.server.migration.mirror; -import io.spine.server.entity.storage.EntityRecordWithColumns; +import io.spine.server.entity.EntityRecord; import io.spine.server.migration.mirror.given.ParcelAgg; import io.spine.server.migration.mirror.given.ParcelId; +import io.spine.server.storage.RecordWithColumns; import io.spine.system.server.Mirror; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -36,10 +37,10 @@ import java.util.function.Function; import static io.spine.server.migration.mirror.given.DeliveryService.generateCourier; -import static io.spine.server.migration.mirror.given.MirrorToEntityRecordTestEnv.assertLifecycleColumns; -import static io.spine.server.migration.mirror.given.MirrorToEntityRecordTestEnv.assertStateColumns; import static io.spine.server.migration.mirror.given.DeliveryService.generateInProgressParcel; import static io.spine.server.migration.mirror.given.MirrorToEntityRecordTestEnv.assertEntityRecord; +import static io.spine.server.migration.mirror.given.MirrorToEntityRecordTestEnv.assertLifecycleColumns; +import static io.spine.server.migration.mirror.given.MirrorToEntityRecordTestEnv.assertStateColumns; import static io.spine.server.migration.mirror.given.MirrorToEntityRecordTestEnv.mirror; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -49,7 +50,7 @@ final class MirrorToEntityRecordTest { @Test @DisplayName("transform a `Mirror` into an `EntityRecordWithColumns`") void mapMirrorToEntityRecord() { - Function> transformation = + Function> transformation = new MirrorToEntityRecord<>(ParcelAgg.class); var parcel = generateInProgressParcel(); diff --git a/server/src/test/java/io/spine/server/projection/ProjectionColumnTest.java b/server/src/test/java/io/spine/server/projection/ProjectionColumnTest.java index 9a1269e2fc..00bd8e89df 100644 --- a/server/src/test/java/io/spine/server/projection/ProjectionColumnTest.java +++ b/server/src/test/java/io/spine/server/projection/ProjectionColumnTest.java @@ -30,9 +30,10 @@ import io.spine.client.DeletedColumn; import io.spine.client.VersionColumn; import io.spine.query.ColumnName; -import io.spine.server.entity.storage.EntityRecordSpec; -import io.spine.server.projection.given.SavedString; +import io.spine.server.entity.EntityRecord; +import io.spine.server.entity.storage.SpecScanner; import io.spine.server.projection.given.SavingProjection; +import io.spine.server.storage.MessageRecordSpec; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -43,8 +44,8 @@ @DisplayName("`Projection` should have columns") class ProjectionColumnTest { - private static final EntityRecordSpec recordSpec = - EntityRecordSpec.of(SavingProjection.class); + private static final MessageRecordSpec recordSpec = + SpecScanner.scan(SavingProjection.class); @Test @DisplayName("`version`") diff --git a/server/src/test/java/io/spine/server/storage/given/EntityRecordStorageTestEnv.java b/server/src/test/java/io/spine/server/storage/given/EntityRecordStorageTestEnv.java index b5c0684ee7..2ecd775879 100644 --- a/server/src/test/java/io/spine/server/storage/given/EntityRecordStorageTestEnv.java +++ b/server/src/test/java/io/spine/server/storage/given/EntityRecordStorageTestEnv.java @@ -40,10 +40,12 @@ import io.spine.server.entity.LifecycleFlags; import io.spine.server.entity.TestTransaction; import io.spine.server.entity.TransactionalEntity; -import io.spine.server.entity.storage.EntityRecordSpec; import io.spine.server.entity.storage.EntityRecordStorage; import io.spine.server.entity.storage.EntityRecordWithColumns; +import io.spine.server.entity.storage.SpecScanner; import io.spine.server.entity.storage.given.TaskViewProjection; +import io.spine.server.storage.MessageRecordSpec; +import io.spine.server.storage.RecordWithColumns; import io.spine.test.entity.TaskView; import io.spine.test.entity.TaskViewId; import io.spine.test.storage.StgProject; @@ -74,6 +76,9 @@ public final class EntityRecordStorageTestEnv { private EntityRecordStorageTestEnv() { } + private static final MessageRecordSpec spec = + SpecScanner.scan(TestCounterEntity.class); + public static EntityRecord buildStorageRecord(StgProjectId id, EntityState state) { var wrappedState = pack(state); @@ -128,16 +133,17 @@ public static void delete(TransactionalEntity entity) { TestTransaction.delete(entity); } - public static EntityRecordWithColumns withLifecycleColumns(I id, EntityRecord record) { - var result = EntityRecordWithColumns.create(id, record); + public static RecordWithColumns + withLifecycleColumns(EntityRecord record) { + var result = RecordWithColumns.create(record, spec); return result; } - public static List> + public static List> recordsWithColumnsFrom(Map recordMap) { - return recordMap.entrySet() + return recordMap.values() .stream() - .map(entry -> withLifecycleColumns(entry.getKey(), entry.getValue())) + .map(EntityRecordStorageTestEnv::withLifecycleColumns) .collect(toList()); } @@ -166,9 +172,9 @@ var record = EntityRecord.newBuilder() return record; } - public static EntityRecordWithColumns + public static RecordWithColumns recordWithCols(Entity entity, EntityRecord record) { - return EntityRecordWithColumns.create(entity, record); + return RecordWithColumns.create(record, SpecScanner.scan(entity)); } public static EntityRecordWithColumns @@ -188,8 +194,8 @@ public static void assertQueryHasSingleResult( return ImmutableSet.of(name(), estimateInDays(), status(), dueDate()); } - public static EntityRecordSpec spec() { - return EntityRecordSpec.of(TaskViewProjection.class); + public static MessageRecordSpec spec() { + return SpecScanner.scan(TaskViewProjection.class); } @SuppressWarnings("unused") // Reflective access From e4b27a100a4f9676a182bcb8b54f7cc8b0ea9831 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Sat, 28 Oct 2023 18:09:23 +0100 Subject: [PATCH 20/55] Migrate more types to `MessageRecordSpec`, start migrating tests. --- .../spine/server/entity/StorageConverter.java | 44 ++- .../entity/storage/EntityRecordStorage.java | 9 +- .../spine/server/entity/storage/Scanner.java | 4 +- .../server/entity/storage/SpecScanner.java | 39 +- .../spine/server/storage/MessageStorage.java | 12 - .../spine/server/storage/RecordStorage.java | 6 +- .../server/storage/RecordStorageDelegate.java | 2 +- .../spine/server/storage/StorageFactory.java | 2 +- .../storage/memory/InMemoryRecordStorage.java | 4 +- .../memory/InMemoryStorageFactory.java | 4 +- .../system/SystemAwareStorageFactory.java | 4 +- .../storage/EntityRecordWithColumnsTest.java | 363 +++++++++--------- .../mirror/given/PreparedStorageFactory.java | 4 +- .../storage/ModelTestStorageFactory.java | 4 +- .../storage/EntityRecordStorageTest.java | 72 ++-- .../given/EntityRecordStorageTestEnv.java | 8 + .../storage/given/GivenStorageProject.java | 20 + .../memory/RecordQueryMatcherTest.java | 33 +- .../system/given/MemoizingStorageFactory.java | 4 +- 19 files changed, 360 insertions(+), 278 deletions(-) diff --git a/server/src/main/java/io/spine/server/entity/StorageConverter.java b/server/src/main/java/io/spine/server/entity/StorageConverter.java index 1569664715..19ed30d527 100644 --- a/server/src/main/java/io/spine/server/entity/StorageConverter.java +++ b/server/src/main/java/io/spine/server/entity/StorageConverter.java @@ -28,6 +28,7 @@ import com.google.common.base.Converter; import com.google.protobuf.FieldMask; +import io.spine.annotation.Internal; import io.spine.base.EntityState; import io.spine.base.Identifier; import io.spine.type.TypeUrl; @@ -97,13 +98,40 @@ protected FieldMask fieldMask() { @Override protected EntityRecord doForward(E entity) { + var builder = toEntityRecord(entity); + updateBuilder(builder, entity); + return builder.build(); + } + + /** + * Creates a new builder of {@code EntityRecord} on top of the passed {@code Entity} state. + * + *

This method is internal to the framework. End-users should rely on other public + * endpoints of {@code StorageConverter}. + * + * @param entity + * an entity to create a record builder from + * @param + * type of entity identifiers + * @param + * type of entity + * @param + * type of entity state + * @return a new entity record builder reflecting the current state of the passed entity + */ + @Internal + public static , S extends EntityState> + EntityRecord.Builder toEntityRecord(E entity) { + checkNotNull(entity); + var entityId = Identifier.pack(entity.id()); var stateAny = pack(entity.state()); var builder = EntityRecord.newBuilder() .setEntityId(entityId) - .setState(stateAny); - updateBuilder(builder, entity); - return builder.build(); + .setState(stateAny) + .setVersion(entity.version()) + .setLifecycleFlags(entity.lifecycleFlags()); + return builder; } @SuppressWarnings("unchecked" /* The cast is safe since the and types are bound with @@ -120,20 +148,22 @@ protected E doBackward(EntityRecord entityRecord) { } /** - * Sets lifecycle flags in the builder from the entity. + * Updates the builder with required values, if needed. * *

Derived classes may override to additionally tune * the passed entity builder. * + *

By default, this method does nothing. + * * @param builder * the entity builder to update * @param entity * the entity which data is passed to the {@link EntityRecord} we are building */ - @SuppressWarnings("WeakerAccess" /* To allow overriding from outside of this package. */) + // TODO:alex.tymchenko:2023-10-28: make abstract? Or kill? + @SuppressWarnings({"WeakerAccess", "unused"}) protected void updateBuilder(EntityRecord.Builder builder, E entity) { - builder.setVersion(entity.version()) - .setLifecycleFlags(entity.lifecycleFlags()); + // Do nothing by default. } /** diff --git a/server/src/main/java/io/spine/server/entity/storage/EntityRecordStorage.java b/server/src/main/java/io/spine/server/entity/storage/EntityRecordStorage.java index 2880efb03e..621b97483a 100644 --- a/server/src/main/java/io/spine/server/entity/storage/EntityRecordStorage.java +++ b/server/src/main/java/io/spine/server/entity/storage/EntityRecordStorage.java @@ -46,6 +46,7 @@ import java.util.Iterator; +import static com.google.common.base.Preconditions.checkState; import static io.spine.server.entity.model.EntityClass.asEntityClass; import static io.spine.server.entity.model.EntityClass.stateClassOf; import static io.spine.server.entity.storage.EntityRecordColumn.archived; @@ -222,8 +223,11 @@ public Iterator readAll(Iterable ids) { */ @Override public synchronized void write(I id, EntityRecord record) { - // TODO:alex.tymchenko:2023-10-27: check that ID matches. var wrapped = RecordWithColumns.create(record, recordSpec()); + checkState(id.equals(wrapped.id()), + "Identifier `%s` passed into `write(id, record)` " + + "does not match the one in the record (`%s`).", + id, wrapped.id()); write(wrapped); } @@ -265,9 +269,8 @@ public boolean delete(I id) { */ @Internal @Override - @SuppressWarnings("unchecked") // Guaranteed by the generic declaration of `EntityRecordSpec`. public final MessageRecordSpec recordSpec() { - return (MessageRecordSpec) super.recordSpec(); + return super.recordSpec(); } private Class idType() { diff --git a/server/src/main/java/io/spine/server/entity/storage/Scanner.java b/server/src/main/java/io/spine/server/entity/storage/Scanner.java index 663843d53b..3ddb89f3e0 100644 --- a/server/src/main/java/io/spine/server/entity/storage/Scanner.java +++ b/server/src/main/java/io/spine/server/entity/storage/Scanner.java @@ -26,11 +26,9 @@ package io.spine.server.entity.storage; -import io.spine.base.EntityState; import io.spine.client.ArchivedColumn; import io.spine.query.Column; import io.spine.query.EntityColumn; -import io.spine.server.entity.Entity; /** * Scans and extracts the definitions of {@link Column}s to be stored @@ -55,7 +53,7 @@ * Such an approach improves the scanning performance and preserve the types of generic * parameters code-generated for each {@code EntityColumn}. */ -final class Scanner, E extends Entity> { +final class Scanner/*, E extends Entity>*/ { // TODO:alex.tymchenko:2023-10-27: kill and use `SpecScanner` under this name. // /** diff --git a/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java b/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java index 92e08e1e93..f37c82ce94 100644 --- a/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java +++ b/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java @@ -35,7 +35,6 @@ import io.spine.client.ArchivedColumn; import io.spine.client.DeletedColumn; import io.spine.client.VersionColumn; -import io.spine.protobuf.AnyPacker; import io.spine.query.Column; import io.spine.query.Column.Getter; import io.spine.query.EntityColumn; @@ -63,8 +62,27 @@ /** * Scans Proto definitions of stored {@code Message}s and determines * the record specification. + * + *

Scans and extracts the definitions of {@link Column}s to be stored + * for a particular {@code Entity}. + * + *

The resulting columns include both the entity state-based columns declared with + * {@link io.spine.option.OptionsProto#column (column)} Proto option and the columns + * storing lifecycle and version attributes of an {@code Entity}. + * + * @implNote Client-side API includes generic definitions of lifecycle and version columns + * (such as {@link ArchivedColumn}). However, their code cannot depend on the {@code Entity} + * type directly, as the {@code client} module has no dependency on {@code server} module. + * Therefore, this column scanning process wires those generic column definitions with an + * actual {@code Entity} type, instances of which serve as a data source for each column. + * Also, instead of scanning the {@code (column)} options from an entity state + * {@code Message} directly, this scanner uses a Spine compiler-generated shortcut method + * called {@code definitions()} which returns the set of {@link EntityColumn}s. + * Such an approach improves the scanning performance and preserve the types of generic + * parameters code-generated for each {@code EntityColumn}. */ @Internal +@SuppressWarnings("Immutable") //// TODO:alex.tymchenko:2023-10-28: address! public final class SpecScanner { /** @@ -87,7 +105,7 @@ public final class SpecScanner { private SpecScanner() { } - static > MessageRecordSpec + public static > MessageRecordSpec scan(Class idClass, Class stateClass) { Set> accumulator = new HashSet<>(); @@ -143,21 +161,30 @@ private MemoizingUnpacker(Class cls) { stateCls = cls; } - private synchronized S unpack(Any value) { + private synchronized S process(Any value) { @Nullable S alreadyUnpacked = cache.get(value); if (alreadyUnpacked != null) { return alreadyUnpacked; } - var state = AnyPacker.unpack(value, stateCls); + var state = unpack(value, stateCls); cache.put(value, state); return state; } } - @NonNull + @SuppressWarnings("ReturnOfNull" /* By design. */) private static > Getter getter(Column stateColumn, MemoizingUnpacker unpacker) { - return r -> requireNonNull(stateColumn.valueIn(unpacker.unpack(r.getState()))); + return r -> { + var state = r.getState(); + if(state.equals(Any.getDefaultInstance())) { + // This may happen for `Aggregate` state, + // if its visibility does not allow querying. + return null; + } + var value = stateColumn.valueIn(unpacker.process(state)); + return requireNonNull(value); + }; } @NonNull diff --git a/server/src/main/java/io/spine/server/storage/MessageStorage.java b/server/src/main/java/io/spine/server/storage/MessageStorage.java index 30c7bbd3c1..2f79c8462a 100644 --- a/server/src/main/java/io/spine/server/storage/MessageStorage.java +++ b/server/src/main/java/io/spine/server/storage/MessageStorage.java @@ -123,18 +123,6 @@ protected void writeBatch(Iterable messages) { writeAll(records); } - /** - * {@inheritDoc} - * - *

Overrides for the type covariance. - */ - @Internal - @Override - @SuppressWarnings("unchecked") // Columns of the stored messages are taken from the messages. - protected RecordSpec recordSpec() { - return (RecordSpec) super.recordSpec(); - } - /** * Extracts the identifier and the columns from the given message, creating * a new {@code RecordWithColumns}. diff --git a/server/src/main/java/io/spine/server/storage/RecordStorage.java b/server/src/main/java/io/spine/server/storage/RecordStorage.java index 55c2d3d11a..64ca7ad4dc 100644 --- a/server/src/main/java/io/spine/server/storage/RecordStorage.java +++ b/server/src/main/java/io/spine/server/storage/RecordStorage.java @@ -56,7 +56,7 @@ @SuppressWarnings("ClassWithTooManyMethods") // This is a centerpiece. public abstract class RecordStorage extends AbstractStorage { - private final RecordSpec recordSpec; + private final MessageRecordSpec recordSpec; /** * Creates the new storage instance. @@ -66,7 +66,7 @@ public abstract class RecordStorage extends AbstractStorag * @param recordSpec * definitions of the columns to store along with each record */ - protected RecordStorage(ContextSpec context, RecordSpec recordSpec) { + protected RecordStorage(ContextSpec context, MessageRecordSpec recordSpec) { super(context.isMultitenant()); this.recordSpec = recordSpec; } @@ -325,7 +325,7 @@ public RecordQueryBuilder queryBuilder() { * Returns the specification of the record format, in which the message record should be stored. */ @Internal - protected RecordSpec recordSpec() { + protected MessageRecordSpec recordSpec() { return recordSpec; } diff --git a/server/src/main/java/io/spine/server/storage/RecordStorageDelegate.java b/server/src/main/java/io/spine/server/storage/RecordStorageDelegate.java index e859c5180c..9cff492f84 100644 --- a/server/src/main/java/io/spine/server/storage/RecordStorageDelegate.java +++ b/server/src/main/java/io/spine/server/storage/RecordStorageDelegate.java @@ -188,7 +188,7 @@ protected boolean deleteRecord(I id) { @Override @Internal - protected RecordSpec recordSpec() { + protected MessageRecordSpec recordSpec() { return delegate.recordSpec(); } diff --git a/server/src/main/java/io/spine/server/storage/StorageFactory.java b/server/src/main/java/io/spine/server/storage/StorageFactory.java index d0826ce623..4d6e183b77 100644 --- a/server/src/main/java/io/spine/server/storage/StorageFactory.java +++ b/server/src/main/java/io/spine/server/storage/StorageFactory.java @@ -97,7 +97,7 @@ public interface StorageFactory extends AutoCloseable { * to create a private instance of a record storage. */ RecordStorage - createRecordStorage(ContextSpec context, RecordSpec recordSpec); + createRecordStorage(ContextSpec context, MessageRecordSpec recordSpec); /** * Creates a new {@link AggregateStorage}. diff --git a/server/src/main/java/io/spine/server/storage/memory/InMemoryRecordStorage.java b/server/src/main/java/io/spine/server/storage/memory/InMemoryRecordStorage.java index 3f1173032e..cf02c8573e 100644 --- a/server/src/main/java/io/spine/server/storage/memory/InMemoryRecordStorage.java +++ b/server/src/main/java/io/spine/server/storage/memory/InMemoryRecordStorage.java @@ -29,7 +29,7 @@ import com.google.protobuf.Message; import io.spine.query.RecordQuery; import io.spine.server.ContextSpec; -import io.spine.server.storage.RecordSpec; +import io.spine.server.storage.MessageRecordSpec; import io.spine.server.storage.RecordStorage; import io.spine.server.storage.RecordWithColumns; @@ -47,7 +47,7 @@ public class InMemoryRecordStorage extends RecordStorage> multitenantStorage; - InMemoryRecordStorage(ContextSpec context, RecordSpec recordSpec) { + InMemoryRecordStorage(ContextSpec context, MessageRecordSpec recordSpec) { super(context, recordSpec); this.multitenantStorage = new MultitenantStorage<>(context.isMultitenant()) { diff --git a/server/src/main/java/io/spine/server/storage/memory/InMemoryStorageFactory.java b/server/src/main/java/io/spine/server/storage/memory/InMemoryStorageFactory.java index 9cbc1a5934..dc0e849b9e 100644 --- a/server/src/main/java/io/spine/server/storage/memory/InMemoryStorageFactory.java +++ b/server/src/main/java/io/spine/server/storage/memory/InMemoryStorageFactory.java @@ -28,7 +28,7 @@ import com.google.protobuf.Message; import io.spine.server.ContextSpec; -import io.spine.server.storage.RecordSpec; +import io.spine.server.storage.MessageRecordSpec; import io.spine.server.storage.StorageFactory; /** @@ -50,7 +50,7 @@ private InMemoryStorageFactory() { @Override public InMemoryRecordStorage - createRecordStorage(ContextSpec context, RecordSpec spec) { + createRecordStorage(ContextSpec context, MessageRecordSpec spec) { return new InMemoryRecordStorage<>(context, spec); } diff --git a/server/src/main/java/io/spine/server/storage/system/SystemAwareStorageFactory.java b/server/src/main/java/io/spine/server/storage/system/SystemAwareStorageFactory.java index 764f76b014..c1804fdd4a 100644 --- a/server/src/main/java/io/spine/server/storage/system/SystemAwareStorageFactory.java +++ b/server/src/main/java/io/spine/server/storage/system/SystemAwareStorageFactory.java @@ -37,7 +37,7 @@ import io.spine.server.delivery.InboxStorage; import io.spine.server.event.EventStore; import io.spine.server.event.store.EmptyEventStore; -import io.spine.server.storage.RecordSpec; +import io.spine.server.storage.MessageRecordSpec; import io.spine.server.storage.RecordStorage; import io.spine.server.storage.StorageFactory; @@ -116,7 +116,7 @@ public EventStore createEventStore(ContextSpec context) { @Override public RecordStorage - createRecordStorage(ContextSpec context, RecordSpec recordSpec) { + createRecordStorage(ContextSpec context, MessageRecordSpec recordSpec) { return delegate.createRecordStorage(context, recordSpec); } diff --git a/server/src/test/java/io/spine/server/entity/storage/EntityRecordWithColumnsTest.java b/server/src/test/java/io/spine/server/entity/storage/EntityRecordWithColumnsTest.java index 47b1201c6c..d21e5d3e6e 100644 --- a/server/src/test/java/io/spine/server/entity/storage/EntityRecordWithColumnsTest.java +++ b/server/src/test/java/io/spine/server/entity/storage/EntityRecordWithColumnsTest.java @@ -58,198 +58,199 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +// TODO:alex.tymchenko:2023-10-28: migrate? @DisplayName("`EntityRecordWithColumns` should") class EntityRecordWithColumnsTest { - private static final StgTaskId TASK_ID = - StgTaskId.newBuilder() - .setId(42) - .build(); - - private static final StgProjectId PROJECT_ID = - StgProjectId.newBuilder() - .setId("42") - .build(); - - private static final Any PACKED_TASK_ID = Identifier.pack(TASK_ID); - - private static EntityRecordWithColumns sampleRecordWithEmptyColumns() { - return EntityRecordWithColumns.of(sampleEntityRecord()); - } - - private static EntityRecordWithColumns randomRecordWithEmptyColumns() { - return EntityRecordWithColumns.of(randomEntityRecord()); - } - - private static EntityRecord sampleEntityRecord() { - return EntityRecord.newBuilder() - .setEntityId(PACKED_TASK_ID) - .build(); - } - - private static EntityRecord randomEntityRecord() { - return EntityRecord.newBuilder() - .setEntityId(Identifier.pack(Identifier.newUuid())) - .build(); - } - - @Test - @DisplayName("not accept `null`s in constructor") - void rejectNullInCtor() { - new NullPointerTester() - .setDefault(EntityRecord.class, sampleEntityRecord()) - .testAllPublicConstructors(EntityRecordWithColumns.class); - } - - @Test - @DisplayName("support equality") - void supportEquality() { - var columnName = ArchivedColumn.instance().name(); - Object value = false; - EntityRecordWithColumns emptyFieldsEnvelope = - EntityRecordWithColumns.of(sampleEntityRecord()); - EntityRecordWithColumns notEmptyFieldsEnvelope = - EntityRecordWithColumns.of(sampleEntityRecord(), singletonMap(columnName, value)); - new EqualsTester() - .addEqualityGroup(emptyFieldsEnvelope) - .addEqualityGroup(notEmptyFieldsEnvelope) - .addEqualityGroup(randomRecordWithEmptyColumns()) - .addEqualityGroup( - randomRecordWithEmptyColumns() // Each one has a different `EntityRecord`. - ) - .testEquals(); - } - - @Test - @DisplayName("return empty names collection if no storage fields are set") - void returnEmptyColumns() { - var record = sampleRecordWithEmptyColumns(); - assertFalse(record.hasColumns()); - var names = record.columnNames(); - assertTrue(names.isEmpty()); - } - - @Test - @DisplayName("throw `ISE` on attempt to get value by non-existent name") - void throwOnNonExistentColumn() { - var record = sampleRecordWithEmptyColumns(); - var nonExistentName = ColumnName.of("non-existent-column"); - assertThrows(IllegalStateException.class, () -> record.columnValue(nonExistentName)); - } - - // TODO:alex.tymchenko:2023-10-27: kill? +// private static final StgTaskId TASK_ID = +// StgTaskId.newBuilder() +// .setId(42) +// .build(); +// +// private static final StgProjectId PROJECT_ID = +// StgProjectId.newBuilder() +// .setId("42") +// .build(); +// +// private static final Any PACKED_TASK_ID = Identifier.pack(TASK_ID); +// +// private static EntityRecordWithColumns sampleRecordWithEmptyColumns() { +// return EntityRecordWithColumns.of(sampleEntityRecord()); +// } +// +// private static EntityRecordWithColumns randomRecordWithEmptyColumns() { +// return EntityRecordWithColumns.of(randomEntityRecord()); +// } +// +// private static EntityRecord sampleEntityRecord() { +// return EntityRecord.newBuilder() +// .setEntityId(PACKED_TASK_ID) +// .build(); +// } +// +// private static EntityRecord randomEntityRecord() { +// return EntityRecord.newBuilder() +// .setEntityId(Identifier.pack(Identifier.newUuid())) +// .build(); +// } +// // @Test -// @DisplayName("have `Entity` lifecycle columns even if the entity does not define custom ones") -// void supportEmptyColumns() { -// var entity = new EntityWithoutCustomColumns(TASK_ID); +// @DisplayName("not accept `null`s in constructor") +// void rejectNullInCtor() { +// new NullPointerTester() +// .setDefault(EntityRecord.class, sampleEntityRecord()) +// .testAllPublicConstructors(EntityRecordWithColumns.class); +// } // -// var columns = EntityRecordSpec.of(entity); -// var storageFields = columns.valuesIn(entity); +// @Test +// @DisplayName("support equality") +// void supportEquality() { +// var columnName = ArchivedColumn.instance().name(); +// Object value = false; +// EntityRecordWithColumns emptyFieldsEnvelope = +// EntityRecordWithColumns.of(sampleEntityRecord()); +// EntityRecordWithColumns notEmptyFieldsEnvelope = +// EntityRecordWithColumns.of(sampleEntityRecord(), singletonMap(columnName, value)); +// new EqualsTester() +// .addEqualityGroup(emptyFieldsEnvelope) +// .addEqualityGroup(notEmptyFieldsEnvelope) +// .addEqualityGroup(randomRecordWithEmptyColumns()) +// .addEqualityGroup( +// randomRecordWithEmptyColumns() // Each one has a different `EntityRecord`. +// ) +// .testEquals(); +// } // +// @Test +// @DisplayName("return empty names collection if no storage fields are set") +// void returnEmptyColumns() { +// var record = sampleRecordWithEmptyColumns(); +// assertFalse(record.hasColumns()); +// var names = record.columnNames(); +// assertTrue(names.isEmpty()); +// } +// +// @Test +// @DisplayName("throw `ISE` on attempt to get value by non-existent name") +// void throwOnNonExistentColumn() { +// var record = sampleRecordWithEmptyColumns(); +// var nonExistentName = ColumnName.of("non-existent-column"); +// assertThrows(IllegalStateException.class, () -> record.columnValue(nonExistentName)); +// } +// +// // TODO:alex.tymchenko:2023-10-27: kill? +//// @Test +//// @DisplayName("have `Entity` lifecycle columns even if the entity does not define custom ones") +//// void supportEmptyColumns() { +//// var entity = new EntityWithoutCustomColumns(TASK_ID); +//// +//// var columns = EntityRecordSpec.of(entity); +//// var storageFields = columns.valuesIn(entity); +//// +//// var record = EntityRecordWithColumns.of(sampleEntityRecord(), storageFields); +//// assertThat(record.columnNames()) +//// .containsExactlyElementsIn(EntityRecordColumn.names()); +//// } +// +// @Test +// @DisplayName("return column value by column name") +// void returnColumnValue() { +// var columnName = ColumnName.of("some-boolean-column"); +// var columnValue = false; +// ImmutableMap storageFields = ImmutableMap.of(columnName, columnValue); // var record = EntityRecordWithColumns.of(sampleEntityRecord(), storageFields); -// assertThat(record.columnNames()) -// .containsExactlyElementsIn(EntityRecordColumn.names()); +// var value = record.columnValue(columnName); +// +// assertThat(value).isEqualTo(columnValue); // } - - @Test - @DisplayName("return column value by column name") - void returnColumnValue() { - var columnName = ColumnName.of("some-boolean-column"); - var columnValue = false; - ImmutableMap storageFields = ImmutableMap.of(columnName, columnValue); - var record = EntityRecordWithColumns.of(sampleEntityRecord(), storageFields); - var value = record.columnValue(columnName); - - assertThat(value).isEqualTo(columnValue); - } - - @Test - @DisplayName("return a column value with the column mapping applied") - void returnValueWithColumnMapping() { - var columnName = ColumnName.of("some-int-column"); - var columnValue = 42; - - ImmutableMap storageFields = ImmutableMap.of(columnName, columnValue); - var record = EntityRecordWithColumns.of(sampleEntityRecord(), storageFields); - var value = record.columnValue(columnName, new TestColumnMapping()); - - assertThat(value).isEqualTo(String.valueOf(columnValue)); - } - - @Test - @DisplayName("return `null` column value") - void returnNullValue() { - var columnName = ColumnName.of("the-null-column"); - Map storageFields = new HashMap<>(); - storageFields.put(columnName, null); - var record = EntityRecordWithColumns.of(sampleEntityRecord(), storageFields); - var value = record.columnValue(columnName); - - assertThat(value).isNull(); - } - - @Nested - @DisplayName("support being initialized with") - class BeInitializedWith { - - // TODO:alex.tymchenko:2023-10-27: kill! +// +// @Test +// @DisplayName("return a column value with the column mapping applied") +// void returnValueWithColumnMapping() { +// var columnName = ColumnName.of("some-int-column"); +// var columnValue = 42; +// +// ImmutableMap storageFields = ImmutableMap.of(columnName, columnValue); +// var record = EntityRecordWithColumns.of(sampleEntityRecord(), storageFields); +// var value = record.columnValue(columnName, new TestColumnMapping()); +// +// assertThat(value).isEqualTo(String.valueOf(columnValue)); +// } +// +// @Test +// @DisplayName("return `null` column value") +// void returnNullValue() { +// var columnName = ColumnName.of("the-null-column"); +// Map storageFields = new HashMap<>(); +// storageFields.put(columnName, null); +// var record = EntityRecordWithColumns.of(sampleEntityRecord(), storageFields); +// var value = record.columnValue(columnName); +// +// assertThat(value).isNull(); +// } +// +// @Nested +// @DisplayName("support being initialized with") +// class BeInitializedWith { +// +// // TODO:alex.tymchenko:2023-10-27: kill! +//// @Test +//// @DisplayName("an `Entity` identifier and a record") +//// void idAndRecord() { +//// var rawRecord = sampleEntityRecord(); +//// Long id = 199L; +//// var result = EntityRecordWithColumns.create(id, rawRecord); +//// assertThat(result).isNotNull(); +//// assertThat(result.id()).isEqualTo(id); +//// assertThat(result.record()).isEqualTo(rawRecord); +//// } +// // @Test -// @DisplayName("an `Entity` identifier and a record") -// void idAndRecord() { +// @DisplayName("an `Entity` and a record") +// void entityAndRecord() { +// var entity = new TestEntity(PROJECT_ID); // var rawRecord = sampleEntityRecord(); -// Long id = 199L; -// var result = EntityRecordWithColumns.create(id, rawRecord); +// var result = RecordWithColumns.create(rawRecord, SpecScanner.scan(entity)); // assertThat(result).isNotNull(); -// assertThat(result.id()).isEqualTo(id); +// assertThat(result.id()).isEqualTo(PROJECT_ID); // assertThat(result.record()).isEqualTo(rawRecord); +// var names = result.columnNames(); +// var expectedNames = +// StgProject.Column.definitions() +// .stream() +// .map(RecordColumn::name) +// .collect(toImmutableSet()); +// assertThat(names).containsAtLeastElementsIn(expectedNames); +// assertThat(names).containsAtLeastElementsIn( +// ImmutableSet.of(ArchivedColumn.instance().name(), +// DeletedColumn.instance().name())); // } - - @Test - @DisplayName("an `Entity` and a record") - void entityAndRecord() { - var entity = new TestEntity(PROJECT_ID); - var rawRecord = sampleEntityRecord(); - var result = RecordWithColumns.create(rawRecord, SpecScanner.scan(entity)); - assertThat(result).isNotNull(); - assertThat(result.id()).isEqualTo(PROJECT_ID); - assertThat(result.record()).isEqualTo(rawRecord); - var names = result.columnNames(); - var expectedNames = - StgProject.Column.definitions() - .stream() - .map(RecordColumn::name) - .collect(toImmutableSet()); - assertThat(names).containsAtLeastElementsIn(expectedNames); - assertThat(names).containsAtLeastElementsIn( - ImmutableSet.of(ArchivedColumn.instance().name(), - DeletedColumn.instance().name())); - } - } - - @Nested - @DisplayName("store") - class Store { - - @Test - @DisplayName("record") - void record() { - var recordWithFields = sampleRecordWithEmptyColumns(); - var record = recordWithFields.record(); - assertNotNull(record); - } - - @Test - @DisplayName("column values") - void columnValues() { - var columnName = DeletedColumn.instance() - .name(); - Object value = false; - var columnsExpected = singletonMap(columnName, value); - var record = EntityRecordWithColumns.of(sampleEntityRecord(), columnsExpected); - var columnNames = record.columnNames(); - assertThat(columnNames).hasSize(1); - assertTrue(columnNames.contains(columnName)); - assertThat(value).isEqualTo(record.columnValue(columnName)); - } - } +// } +// +// @Nested +// @DisplayName("store") +// class Store { +// +// @Test +// @DisplayName("record") +// void record() { +// var recordWithFields = sampleRecordWithEmptyColumns(); +// var record = recordWithFields.record(); +// assertNotNull(record); +// } +// +// @Test +// @DisplayName("column values") +// void columnValues() { +// var columnName = DeletedColumn.instance() +// .name(); +// Object value = false; +// var columnsExpected = singletonMap(columnName, value); +// var record = EntityRecordWithColumns.of(sampleEntityRecord(), columnsExpected); +// var columnNames = record.columnNames(); +// assertThat(columnNames).hasSize(1); +// assertTrue(columnNames.contains(columnName)); +// assertThat(value).isEqualTo(record.columnValue(columnName)); +// } +// } } diff --git a/server/src/test/java/io/spine/server/migration/mirror/given/PreparedStorageFactory.java b/server/src/test/java/io/spine/server/migration/mirror/given/PreparedStorageFactory.java index 4dc07f58ea..38e4855c2e 100644 --- a/server/src/test/java/io/spine/server/migration/mirror/given/PreparedStorageFactory.java +++ b/server/src/test/java/io/spine/server/migration/mirror/given/PreparedStorageFactory.java @@ -31,7 +31,7 @@ import io.spine.server.ContextSpec; import io.spine.server.entity.Entity; import io.spine.server.entity.storage.EntityRecordStorage; -import io.spine.server.storage.RecordSpec; +import io.spine.server.storage.MessageRecordSpec; import io.spine.server.storage.RecordStorage; import io.spine.server.storage.StorageFactory; import io.spine.server.storage.memory.InMemoryStorageFactory; @@ -56,7 +56,7 @@ public void close() { @Override public RecordStorage createRecordStorage( - ContextSpec context, RecordSpec spec) { + ContextSpec context, MessageRecordSpec spec) { return InMemoryStorageFactory.newInstance().createRecordStorage(context, spec); } diff --git a/server/src/test/java/io/spine/server/model/given/storage/ModelTestStorageFactory.java b/server/src/test/java/io/spine/server/model/given/storage/ModelTestStorageFactory.java index 2cf1a07650..affcbfcca8 100644 --- a/server/src/test/java/io/spine/server/model/given/storage/ModelTestStorageFactory.java +++ b/server/src/test/java/io/spine/server/model/given/storage/ModelTestStorageFactory.java @@ -29,7 +29,7 @@ import com.google.protobuf.Message; import io.spine.server.ContextSpec; import io.spine.server.delivery.InboxMessage; -import io.spine.server.storage.RecordSpec; +import io.spine.server.storage.MessageRecordSpec; import io.spine.server.storage.RecordStorage; import io.spine.server.storage.StorageFactory; @@ -51,7 +51,7 @@ public ModelTestStorageFactory(StorageFactory delegate) { @Override public RecordStorage createRecordStorage( ContextSpec context, - RecordSpec recordSpec + MessageRecordSpec recordSpec ) { var storage = delegate.createRecordStorage(context, recordSpec); if (recordSpec.storedType().equals(InboxMessage.class)) { diff --git a/server/src/test/java/io/spine/server/storage/EntityRecordStorageTest.java b/server/src/test/java/io/spine/server/storage/EntityRecordStorageTest.java index 54f8b1b66d..dbe910b062 100644 --- a/server/src/test/java/io/spine/server/storage/EntityRecordStorageTest.java +++ b/server/src/test/java/io/spine/server/storage/EntityRecordStorageTest.java @@ -41,8 +41,8 @@ import io.spine.server.entity.TransactionalEntity; import io.spine.server.entity.storage.EntityRecordStorage; import io.spine.server.entity.storage.EntityRecordWithColumns; -import io.spine.server.storage.given.EntityRecordStorageTestEnv; import io.spine.server.storage.given.EntityRecordStorageTestEnv.TestCounterEntity; +import io.spine.server.storage.given.GivenStorageProject; import io.spine.test.storage.StgProject; import io.spine.test.storage.StgProjectId; import org.checkerframework.checker.nullness.qual.Nullable; @@ -62,7 +62,6 @@ import static com.google.common.collect.Sets.newHashSet; import static com.google.common.truth.Truth.assertThat; import static com.google.protobuf.util.FieldMaskUtil.fromFieldNumbers; -import static io.spine.base.Identifier.newUuid; import static io.spine.protobuf.AnyPacker.unpack; import static io.spine.protobuf.Messages.isDefault; import static io.spine.server.ContextSpec.singleTenant; @@ -71,11 +70,11 @@ import static io.spine.server.storage.given.EntityRecordStorageTestEnv.assertIteratorsEqual; import static io.spine.server.storage.given.EntityRecordStorageTestEnv.assertQueryHasSingleResult; import static io.spine.server.storage.given.EntityRecordStorageTestEnv.assertSingleRecord; -import static io.spine.server.storage.given.EntityRecordStorageTestEnv.buildStorageRecord; import static io.spine.server.storage.given.EntityRecordStorageTestEnv.delete; import static io.spine.server.storage.given.EntityRecordStorageTestEnv.newEntity; import static io.spine.server.storage.given.EntityRecordStorageTestEnv.newRecord; import static io.spine.server.storage.given.EntityRecordStorageTestEnv.recordWithCols; +import static io.spine.server.storage.given.EntityRecordStorageTestEnv.recordsWithColumnsFrom; import static io.spine.server.storage.given.GivenStorageProject.newState; import static io.spine.test.storage.StgProject.Status.CANCELLED; import static io.spine.test.storage.StgProject.Status.CANCELLED_VALUE; @@ -115,9 +114,7 @@ protected EntityRecord newStorageRecord(StgProjectId id) { @Override protected final StgProjectId newId() { - return StgProjectId.newBuilder() - .setId(newUuid()) - .build(); + return GivenStorageProject.newId(); } @Test @@ -165,7 +162,7 @@ void createUniqueStatesForSameId() { for (var i = 0; i < checkCount; i++) { EntityState newState = newState(id); if (states.contains(newState)) { - fail("RecordStorageTest.newState() should return unique messages."); + fail("`RecordStorageTest.newState()` should return unique messages."); } } } @@ -239,8 +236,6 @@ void archivedRecords() { var activeRecordId = newId(); var archivedRecordId = newId(); - var activeRecord = buildStorageRecord(activeRecordId, newState(activeRecordId)); - var archivedRecord = buildStorageRecord(archivedRecordId, newState(archivedRecordId)); TransactionalEntity activeEntity = newEntity(activeRecordId); TransactionalEntity archivedEntity = newEntity(archivedRecordId); @@ -248,11 +243,12 @@ void archivedRecords() { var storage = storage(); - storage.write(recordWithCols(activeEntity, activeRecord)); - storage.write(recordWithCols(archivedEntity, archivedRecord)); + storage.write(recordWithCols(activeEntity)); + var archivedWithCols = recordWithCols(archivedEntity); + storage.write(archivedWithCols); var query = StgProject.query().where(ArchivedColumn.is(), true).build(); - assertQueryHasSingleResult(query, archivedRecord, storage); + assertQueryHasSingleResult(query, archivedWithCols.record(), storage); } } @@ -285,9 +281,9 @@ void byColumns() { // After the mutation above the single matching record is the one // under the `idMatching` ID - var fineRecord = writeRecord(storage, idMatching, matchingEntity); - writeRecord(storage, idWrong1, wrong1); - writeRecord(storage, idWrong2, wrong2); + var fineRecord = writeRecord(storage, matchingEntity); + writeRecord(storage, wrong1); + writeRecord(storage, wrong2); var query = StgProject.query() .projectStatusValue().is(DONE_VALUE) @@ -298,12 +294,10 @@ void byColumns() { @CanIgnoreReturnValue private EntityRecord writeRecord(EntityRecordStorage storage, - StgProjectId id, TestCounterEntity entity) { - var record = buildStorageRecord(id, newState(id)); - var withCols = recordWithCols(entity, record); + var withCols = recordWithCols(entity); storage.write(withCols); - return record; + return withCols.record(); } private Version projectVersion() { @@ -374,10 +368,9 @@ void byIdAndNoColumns() { private EntityRecord writeRecord(StgProjectId id, EntityRecordStorage storage) { Entity entity = newEntity(id); - var record = buildStorageRecord(id, newState(id)); - var withCols = recordWithCols(entity, record); + var withCols = recordWithCols(entity); storage.write(withCols); - return record; + return withCols.record(); } @Test @@ -399,9 +392,9 @@ private EntityRecord writeRecordAndDelete( StgProjectId deletedId, EntityRecordStorage storage) { var deletedEntity = newEntity(deletedId); delete(deletedEntity); - var deletedRecord = buildStorageRecord(deletedEntity); - storage.write(recordWithCols(deletedEntity, deletedRecord)); - return deletedRecord; + var deletedWithCols = recordWithCols(deletedEntity); + storage.write(deletedWithCols); + return deletedWithCols.record(); } @CanIgnoreReturnValue @@ -409,9 +402,9 @@ private EntityRecord writeRecordAndDelete( (StgProjectId archivedId, EntityRecordStorage storage) { var archivedEntity = newEntity(archivedId); archive(archivedEntity); - var archivedRecord = buildStorageRecord(archivedEntity); - storage.write(recordWithCols(archivedEntity, archivedRecord)); - return archivedRecord; + var recordWithCols = recordWithCols(archivedEntity); + storage.write(recordWithCols); + return recordWithCols.record(); } @Test @@ -454,9 +447,7 @@ void filterByIdByDefaultInBulk() { private void write(Entity entity) { var storage = storage(); - var record = buildStorageRecord(entity.id(), entity.state(), - entity.lifecycleFlags()); - storage.write(recordWithCols(entity, record)); + storage.write(recordWithCols(entity)); } } @@ -484,15 +475,14 @@ void withoutColumns() { @DisplayName("a record with custom columns") void withColumns() { var id = newId(); - var record = newStorageRecord(id); Entity testEntity = newEntity(id); var storage = storage(); - var withCols = recordWithCols(testEntity, record); + var withCols = recordWithCols(testEntity); storage.write(withCols); var readRecord = storage.read(id); assertTrue(readRecord.isPresent()); - assertEquals(record, readRecord.get()); + assertEquals(withCols.record(), readRecord.get()); } @Test @@ -547,11 +537,11 @@ var record = newStorageRecord(id); v2Records.put(id, alternateRecord); } - storage.writeAll(EntityRecordStorageTestEnv.recordsWithColumnsFrom(v1Records)); + storage.writeAll(recordsWithColumnsFrom(v1Records)); var firstRevision = storage.readAll(); assertIteratorsEqual(v1Records.values() .iterator(), firstRevision); - storage.writeAll(EntityRecordStorageTestEnv.recordsWithColumnsFrom(v2Records)); + storage.writeAll(recordsWithColumnsFrom(v2Records)); var secondRevision = storage.readAll(); assertIteratorsEqual(v2Records.values() .iterator(), secondRevision); @@ -563,11 +553,10 @@ void updateColumnValues() { var storage = storage(); var id = newId(); var entity = newEntity(id); - var record = buildStorageRecord(id, newState(id)); // Write with `DONE` status at first. var initialStatus = DONE; - record = writeWithStatus(entity, initialStatus, record, storage); + var record = writeWithStatus(entity, initialStatus, storage); var query = StgProject.query() .projectStatusValue().is(initialStatus.getNumber()) @@ -577,7 +566,7 @@ record = writeWithStatus(entity, initialStatus, record, storage); // Update the column status with `CANCELLED` value. var statusAfterUpdate = CANCELLED; - writeWithStatus(entity, statusAfterUpdate, record, storage); + writeWithStatus(entity, statusAfterUpdate, storage); var recordsAfter = storage.findAll(query); assertFalse(recordsAfter.hasNext()); @@ -587,12 +576,11 @@ record = writeWithStatus(entity, initialStatus, record, storage); private EntityRecord writeWithStatus(TestCounterEntity entity, StgProject.Status status, - EntityRecord record, EntityRecordStorage storage) { entity.assignStatus(status); - var withCols = recordWithCols(entity, record); + var withCols = recordWithCols(entity); storage.write(withCols); - return record; + return withCols.record(); } } } diff --git a/server/src/test/java/io/spine/server/storage/given/EntityRecordStorageTestEnv.java b/server/src/test/java/io/spine/server/storage/given/EntityRecordStorageTestEnv.java index 2ecd775879..0834d9c622 100644 --- a/server/src/test/java/io/spine/server/storage/given/EntityRecordStorageTestEnv.java +++ b/server/src/test/java/io/spine/server/storage/given/EntityRecordStorageTestEnv.java @@ -38,6 +38,7 @@ import io.spine.server.entity.EntityRecord; import io.spine.server.entity.HasLifecycleColumns; import io.spine.server.entity.LifecycleFlags; +import io.spine.server.entity.StorageConverter; import io.spine.server.entity.TestTransaction; import io.spine.server.entity.TransactionalEntity; import io.spine.server.entity.storage.EntityRecordStorage; @@ -177,6 +178,13 @@ var record = EntityRecord.newBuilder() return RecordWithColumns.create(record, SpecScanner.scan(entity)); } + public static RecordWithColumns + recordWithCols(Entity entity) { + var record = StorageConverter.toEntityRecord(entity) + .build(); + return RecordWithColumns.create(record, SpecScanner.scan(entity)); + } + public static EntityRecordWithColumns newRecord(StgProjectId id, EntityRecord record) { return EntityRecordWithColumns.create(id, record); diff --git a/server/src/test/java/io/spine/server/storage/given/GivenStorageProject.java b/server/src/test/java/io/spine/server/storage/given/GivenStorageProject.java index 681d8caa07..d5a1c2afbc 100644 --- a/server/src/test/java/io/spine/server/storage/given/GivenStorageProject.java +++ b/server/src/test/java/io/spine/server/storage/given/GivenStorageProject.java @@ -34,6 +34,7 @@ import io.spine.test.storage.StgProjectId; import io.spine.test.storage.StgTask; +import static io.spine.base.Identifier.newUuid; import static io.spine.base.Time.currentTime; import static java.lang.String.format; import static java.lang.System.nanoTime; @@ -88,4 +89,23 @@ public static StgProject newState(StgProjectId id, Status status, Timestamp dueD .build(); return project; } + + /** + * Generates new identifier of {@code StgProjectId} type. + */ + public static StgProjectId newId() { + return StgProjectId.newBuilder() + .setId(newUuid()) + .build(); + } + + /** + * Creates a randomly generated {@code StgProject}. + * + * @see #newState(StgProjectId) + */ + public static StgProject newState() { + var id = newId(); + return newState(id); + } } diff --git a/server/src/test/java/io/spine/server/storage/memory/RecordQueryMatcherTest.java b/server/src/test/java/io/spine/server/storage/memory/RecordQueryMatcherTest.java index a0450fed4f..5eb18ace64 100644 --- a/server/src/test/java/io/spine/server/storage/memory/RecordQueryMatcherTest.java +++ b/server/src/test/java/io/spine/server/storage/memory/RecordQueryMatcherTest.java @@ -28,19 +28,31 @@ import com.google.common.collect.ImmutableMap; import io.spine.base.Identifier; +import io.spine.protobuf.AnyPacker; import io.spine.query.ColumnName; import io.spine.query.RecordQuery; import io.spine.query.RecordQueryBuilder; import io.spine.server.entity.EntityRecord; +import io.spine.server.entity.StorageConverter; import io.spine.server.entity.storage.EntityRecordWithColumns; +import io.spine.server.entity.storage.SpecScanner; +import io.spine.server.storage.MessageRecordSpec; +import io.spine.server.storage.RecordWithColumns; +import io.spine.server.storage.given.GivenStorageProject; import io.spine.test.entity.ProjectId; import io.spine.test.entity.TaskId; +import io.spine.test.storage.StgProject; +import io.spine.test.storage.StgProjectId; import io.spine.testdata.Sample; +import io.spine.testing.core.given.GivenVersion; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.util.Map; +import static io.spine.base.Identifier.pack; +import static io.spine.base.Identifier.pack; +import static io.spine.protobuf.AnyPacker.pack; import static io.spine.server.storage.memory.given.RecordQueryMatcherTestEnv.anyColumn; import static io.spine.server.storage.memory.given.RecordQueryMatcherTestEnv.anyValue; import static io.spine.server.storage.memory.given.RecordQueryMatcherTestEnv.booleanColumn; @@ -53,6 +65,9 @@ @DisplayName("`RecordQueryMatcher` should") class RecordQueryMatcherTest { + private static final MessageRecordSpec spec = + SpecScanner.scan(StgProjectId.class, StgProject.class); + @Test @DisplayName("match everything except `null` to empty query") void matchEverythingToEmpty() { @@ -60,7 +75,7 @@ void matchEverythingToEmpty() { RecordQueryMatcher matcher = new RecordQueryMatcher<>(sampleSubject); assertFalse(matcher.test(nullRef())); - assertTrue(matcher.test(EntityRecordWithColumns.of(sampleEntityRecord()))); + assertTrue(matcher.test(RecordWithColumns.create(sampleEntityRecord(), )); } @Test @@ -71,9 +86,9 @@ void matchIds() { var matcher = new RecordQueryMatcher<>(subject); var matching = sampleEntityRecord(genericId); - var nonMatching = sampleEntityRecord(Sample.messageOfType(ProjectId.class)); - EntityRecordWithColumns matchingRecord = EntityRecordWithColumns.of(matching); - EntityRecordWithColumns nonMatchingRecord = EntityRecordWithColumns.of(nonMatching); + var nonMatching = sampleEntityRecord(Sample.messageOfType(StgProject.class)); + var matchingRecord = EntityRecordWithColumns.of(matching); + var nonMatchingRecord = EntityRecordWithColumns.of(nonMatching); assertTrue(matcher.test(matchingRecord)); assertFalse(matcher.test(nonMatchingRecord)); } @@ -88,7 +103,7 @@ void matchColumns() { var matcher = new RecordQueryMatcher<>(query.subject()); - var matching = sampleEntityRecord(Sample.messageOfType(TaskId.class)); + var matching = sampleEntityRecord(); Map matchingColumns = ImmutableMap.of(columnName, actualValue); var matchingRecord = EntityRecordWithColumns.of(matching, matchingColumns); @@ -132,12 +147,16 @@ private static RecordQueryBuilder newBuilder() { } private static EntityRecord sampleEntityRecord() { - return sampleEntityRecord(Identifier.newUuid()); + var id = GivenStorageProject.newId(); + return sampleEntityRecord(id); } - private static EntityRecord sampleEntityRecord(Object id) { + private static EntityRecord sampleEntityRecord(StgProjectId id) { + var state = GivenStorageProject.newState(id); return EntityRecord.newBuilder() .setEntityId(Identifier.pack(id)) + .setState(pack(state)) + .setVersion(GivenVersion.withNumber(11)) .build(); } } diff --git a/server/src/test/java/io/spine/server/storage/system/given/MemoizingStorageFactory.java b/server/src/test/java/io/spine/server/storage/system/given/MemoizingStorageFactory.java index 55d7007df8..0b76f2567d 100644 --- a/server/src/test/java/io/spine/server/storage/system/given/MemoizingStorageFactory.java +++ b/server/src/test/java/io/spine/server/storage/system/given/MemoizingStorageFactory.java @@ -35,7 +35,7 @@ import io.spine.server.delivery.CatchUpStorage; import io.spine.server.delivery.InboxStorage; import io.spine.server.event.EventStore; -import io.spine.server.storage.RecordSpec; +import io.spine.server.storage.MessageRecordSpec; import io.spine.server.storage.RecordStorage; import io.spine.server.storage.StorageFactory; @@ -83,7 +83,7 @@ public EventStore createEventStore(ContextSpec context) { @Override public RecordStorage - createRecordStorage(ContextSpec context, RecordSpec recordSpec) { + createRecordStorage(ContextSpec context, MessageRecordSpec recordSpec) { requestedStorages.add(recordSpec.storedType()); return nullRef(); } From 8f0d2a3b2340bb6da44ec1f47f80095a5a64ba95 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Sat, 28 Oct 2023 18:09:32 +0100 Subject: [PATCH 21/55] Kill unused type. --- .../server/entity/storage/AsEntityColumn.java | 88 ------------------- 1 file changed, 88 deletions(-) delete mode 100644 server/src/main/java/io/spine/server/entity/storage/AsEntityColumn.java diff --git a/server/src/main/java/io/spine/server/entity/storage/AsEntityColumn.java b/server/src/main/java/io/spine/server/entity/storage/AsEntityColumn.java deleted file mode 100644 index 98f233e82b..0000000000 --- a/server/src/main/java/io/spine/server/entity/storage/AsEntityColumn.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2022, 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.entity.storage; - -import io.spine.base.EntityState; -import io.spine.query.Column; -import io.spine.query.ColumnName; -import io.spine.server.entity.Entity; -import org.checkerframework.checker.nullness.qual.Nullable; - -import java.util.function.Function; - -/** - * A type of {@link Column}s which values are calculated - * on top of an instance of {@code Entity} state. - * - *

The returning column preserves the name and the type of the wrapped original column. - * - *

Callee must supply a new getter to extract the value for the resulting column from - * the {@code Entity} state. However, due to the limitations of Java generics, the type of - * the returning value is not checked to be the same as the original type of the wrapped column. - * See the implementation note on this matter. - * - * @param - * the type of {@code Entity} state serving as a source for the column value - * @implNote This class uses a raw form of the returning value for - * {@link #type() Class type()} method, since it is not possible to use the type of the - * wrapped column to parameterize the type of the returning value of {@code Class}. - */ -// TODO:alex.tymchenko:2023-10-27: kill! -@SuppressWarnings({"rawtypes", "unchecked"}) /* See the impl. note. */ -final class AsEntityColumn> implements Column { - - private final Column column; - private final Function getter; - - /** - * Creates a new instance of the adapter-column. - * - * @param column - * the column to wrap - * @param getter - * a getter extracting the column value from the {@code Entity} state - */ - AsEntityColumn(Column column, Function getter) { - this.column = column; - this.getter = getter; - } - - @Override - public ColumnName name() { - return column.name(); - } - - @Override - public Class type() { - return column.type(); - } - - @Override - public @Nullable Object valueIn(S source) { - return getter.apply(source); - } -} From 06ed95a8004ab9988dc24f411007a487c4aa7042 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 30 Oct 2023 17:24:21 +0000 Subject: [PATCH 22/55] Update `RecordQueryMatcher` tests. --- .../memory/RecordQueryMatcherTest.java | 138 +++++++----------- .../given/RecordQueryMatcherTestEnv.java | 99 +++++++------ 2 files changed, 106 insertions(+), 131 deletions(-) diff --git a/server/src/test/java/io/spine/server/storage/memory/RecordQueryMatcherTest.java b/server/src/test/java/io/spine/server/storage/memory/RecordQueryMatcherTest.java index 5eb18ace64..7441f6816c 100644 --- a/server/src/test/java/io/spine/server/storage/memory/RecordQueryMatcherTest.java +++ b/server/src/test/java/io/spine/server/storage/memory/RecordQueryMatcherTest.java @@ -26,137 +26,99 @@ package io.spine.server.storage.memory; -import com.google.common.collect.ImmutableMap; -import io.spine.base.Identifier; -import io.spine.protobuf.AnyPacker; -import io.spine.query.ColumnName; -import io.spine.query.RecordQuery; -import io.spine.query.RecordQueryBuilder; -import io.spine.server.entity.EntityRecord; -import io.spine.server.entity.StorageConverter; -import io.spine.server.entity.storage.EntityRecordWithColumns; -import io.spine.server.entity.storage.SpecScanner; -import io.spine.server.storage.MessageRecordSpec; import io.spine.server.storage.RecordWithColumns; -import io.spine.server.storage.given.GivenStorageProject; -import io.spine.test.entity.ProjectId; -import io.spine.test.entity.TaskId; -import io.spine.test.storage.StgProject; +import io.spine.server.storage.memory.given.RecordQueryMatcherTestEnv; +import io.spine.server.storage.memory.given.RecordQueryMatcherTestEnv.StgProjectColumns; import io.spine.test.storage.StgProjectId; -import io.spine.testdata.Sample; -import io.spine.testing.core.given.GivenVersion; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.util.Map; - -import static io.spine.base.Identifier.pack; -import static io.spine.base.Identifier.pack; +import static com.google.common.truth.Truth.assertThat; import static io.spine.protobuf.AnyPacker.pack; -import static io.spine.server.storage.memory.given.RecordQueryMatcherTestEnv.anyColumn; -import static io.spine.server.storage.memory.given.RecordQueryMatcherTestEnv.anyValue; -import static io.spine.server.storage.memory.given.RecordQueryMatcherTestEnv.booleanColumn; +import static io.spine.server.storage.given.GivenStorageProject.newState; +import static io.spine.server.storage.memory.given.RecordQueryMatcherTestEnv.newBuilder; import static io.spine.server.storage.memory.given.RecordQueryMatcherTestEnv.recordSubject; +import static io.spine.server.storage.memory.given.RecordQueryMatcherTestEnv.projectSpec; +import static io.spine.testdata.Sample.messageOfType; import static io.spine.testing.TestValues.nullRef; -import static java.util.Collections.singletonMap; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; @DisplayName("`RecordQueryMatcher` should") class RecordQueryMatcherTest { - private static final MessageRecordSpec spec = - SpecScanner.scan(StgProjectId.class, StgProject.class); - @Test @DisplayName("match everything except `null` to empty query") void matchEverythingToEmpty() { var sampleSubject = recordSubject(); - RecordQueryMatcher matcher = new RecordQueryMatcher<>(sampleSubject); + var matcher = new RecordQueryMatcher<>(sampleSubject); - assertFalse(matcher.test(nullRef())); - assertTrue(matcher.test(RecordWithColumns.create(sampleEntityRecord(), )); + assertThat(matcher.test(nullRef())) + .isFalse(); + assertThat(matcher.test(RecordWithColumns.create(newState(), projectSpec()))) + .isTrue(); } @Test @DisplayName("match IDs") void matchIds() { - var genericId = Sample.messageOfType(ProjectId.class); - var subject = recordSubject(genericId); + var matchingId = messageOfType(StgProjectId.class); + var nonMatchingId = messageOfType(StgProjectId.class); + var subject = recordSubject(matchingId); var matcher = new RecordQueryMatcher<>(subject); - var matching = sampleEntityRecord(genericId); - var nonMatching = sampleEntityRecord(Sample.messageOfType(StgProject.class)); - var matchingRecord = EntityRecordWithColumns.of(matching); - var nonMatchingRecord = EntityRecordWithColumns.of(nonMatching); - assertTrue(matcher.test(matchingRecord)); - assertFalse(matcher.test(nonMatchingRecord)); + var matching = newState(matchingId); + var nonMatching = newState(nonMatchingId); + var matchingRecord = RecordWithColumns.create(matching, projectSpec()); + var nonMatchingRecord = RecordWithColumns.create(nonMatching, projectSpec()); + assertThat(matcher.test(matchingRecord)) + .isTrue(); + assertThat(matcher.test(nonMatchingRecord)) + .isFalse(); } @Test @DisplayName("match columns") void matchColumns() { - var column = booleanColumn(); - var actualValue = false; - var columnName = column.name(); - var query = newBuilder().where(column).is(actualValue).build(); - + var matchingState = newState(); + var matchingName = matchingState.getName(); + var query = newBuilder().where(StgProjectColumns.name).is(matchingName).build(); var matcher = new RecordQueryMatcher<>(query.subject()); - - var matching = sampleEntityRecord(); - Map matchingColumns = ImmutableMap.of(columnName, actualValue); - var matchingRecord = EntityRecordWithColumns.of(matching, matchingColumns); - - var nonMatching = sampleEntityRecord(Sample.messageOfType(TaskId.class)); - var nonMatchingRecord = EntityRecordWithColumns.of(nonMatching); - - assertTrue(matcher.test(matchingRecord)); - assertFalse(matcher.test(nonMatchingRecord)); + var matchingRecord = RecordWithColumns.create(matchingState, projectSpec()); + var nonMatching = newState(); + var nonMatchingRecord = RecordWithColumns.create(nonMatching, projectSpec()); + + assertThat(matcher.test(matchingRecord)) + .isTrue(); + assertThat(matcher.test(nonMatchingRecord)) + .isFalse(); } @Test @DisplayName("match `Any` instances") void matchAnyInstances() { - var column = anyColumn(); - var actualValue = anyValue(); + var matchingState = newState(); + var queryValue = pack(matchingState); + var matchingRecord = RecordWithColumns.create(matchingState, projectSpec()); + var nonMatchingRecord = RecordWithColumns.create(newState(), projectSpec()); - var columnName = column.name(); - - var record = sampleEntityRecord(); - Map columns = singletonMap(columnName, actualValue); - var recordAndCols = EntityRecordWithColumns.of(record, columns); - var query = newBuilder().where(column).is(actualValue).build(); + var query = newBuilder() + .where(StgProjectColumns.state_as_any).is(queryValue).build(); var matcher = new RecordQueryMatcher<>(query); - assertTrue(matcher.test(recordAndCols)); + assertThat(matcher.test(matchingRecord)) + .isTrue(); + assertThat(matcher.test(nonMatchingRecord)) + .isFalse(); } @Test @DisplayName("not match by wrong field name") void notMatchByWrongField() { - var target = booleanColumn("some_random_name"); - var query = newBuilder().where(target).is(true).build(); + var query = newBuilder() + .where(StgProjectColumns.random_non_stored_column).is("whatever") + .build(); var matcher = new RecordQueryMatcher<>(query); - var record = sampleEntityRecord(); - var recordWithColumns = EntityRecordWithColumns.of(record); - assertFalse(matcher.test(recordWithColumns)); - } - - private static RecordQueryBuilder newBuilder() { - return RecordQuery.newBuilder(Object.class, EntityRecord.class); - } - - private static EntityRecord sampleEntityRecord() { - var id = GivenStorageProject.newId(); - return sampleEntityRecord(id); - } - - private static EntityRecord sampleEntityRecord(StgProjectId id) { - var state = GivenStorageProject.newState(id); - return EntityRecord.newBuilder() - .setEntityId(Identifier.pack(id)) - .setState(pack(state)) - .setVersion(GivenVersion.withNumber(11)) - .build(); + var recordWithColumns = RecordWithColumns.create(newState(), projectSpec()); + assertThat(matcher.test(recordWithColumns)) + .isFalse(); } } diff --git a/server/src/test/java/io/spine/server/storage/memory/given/RecordQueryMatcherTestEnv.java b/server/src/test/java/io/spine/server/storage/memory/given/RecordQueryMatcherTestEnv.java index 8b88cd1792..1e9e75f2e8 100644 --- a/server/src/test/java/io/spine/server/storage/memory/given/RecordQueryMatcherTestEnv.java +++ b/server/src/test/java/io/spine/server/storage/memory/given/RecordQueryMatcherTestEnv.java @@ -27,15 +27,17 @@ package io.spine.server.storage.memory.given; import com.google.protobuf.Any; +import com.google.protobuf.Timestamp; import io.spine.protobuf.AnyPacker; +import io.spine.query.Columns; import io.spine.query.RecordColumn; +import io.spine.query.RecordColumns; import io.spine.query.RecordQuery; +import io.spine.query.RecordQueryBuilder; import io.spine.query.Subject; -import io.spine.server.entity.EntityRecord; +import io.spine.server.storage.MessageRecordSpec; import io.spine.test.storage.StgProject; -import io.spine.testdata.Sample; - -import static io.spine.query.RecordColumn.create; +import io.spine.test.storage.StgProjectId; /** * The test environment for {@link io.spine.server.storage.memory.RecordQueryMatcher} tests. @@ -46,69 +48,80 @@ @SuppressWarnings("BadImport") // `create` looks fine in this context. public final class RecordQueryMatcherTestEnv { + private static final MessageRecordSpec spec = + new MessageRecordSpec<>(StgProjectId.class, + StgProject.class, + StgProject::getId, + StgProjectColumns.definitions()); + /** Prevents instantiation of this test env class. */ private RecordQueryMatcherTestEnv() { } /** - * Creates an empty {@code Subject} for the {@link EntityRecord}. + * Returns the record specification for {@code StgProject}. */ - public static Subject recordSubject() { - return RecordQuery.newBuilder(Object.class, EntityRecord.class) - .build() - .subject(); + public static MessageRecordSpec projectSpec() { + return spec; } /** - * Creates a {@code Subject} for the {@link EntityRecord} with the given ID. + * Creates an empty {@code Subject} for the {@code StgProject}. */ - public static Subject recordSubject(I id) { - return RecordQuery.newBuilder(parameterizedClsOf(id), EntityRecord.class) - .id().is(id) - .build() - .subject(); - } - - @SuppressWarnings("unchecked") // as per the declaration. - private static Class parameterizedClsOf(I id) { - return (Class) id.getClass(); + public static Subject recordSubject() { + return RecordQuery.newBuilder(StgProjectId.class, StgProject.class) + .build() + .subject(); } /** - * A {@code Column} which holds an {@link Any} instance. + * Creates a {@code Subject} for the {@code EntityRecord} with the given ID. */ - public static RecordColumn anyColumn() { - return create("wrapped_state", Any.class, (r) -> anyValue()); + public static Subject recordSubject(StgProjectId id) { + return RecordQuery.newBuilder(parameterizedClsOf(id), StgProject.class) + .id().is(id) + .build() + .subject(); } - /** - * The {@link Any} value held by the corresponding {@linkplain #anyColumn() entity column}. - */ - public static Any anyValue() { - var someMessage = Sample.messageOfType(StgProject.class); - var value = AnyPacker.pack(someMessage); - return value; + @SuppressWarnings("unchecked" /* As per the declaration. */) + private static Class parameterizedClsOf(I id) { + return (Class) id.getClass(); } /** - * A {@code Column} which holds a {@code boolean} value. + * Creates a new query builder targeting stored {@code StgProject} instances. */ - public static RecordColumn booleanColumn() { - return create("internal", Boolean.class, (r) -> booleanValue()); + public static RecordQueryBuilder newBuilder() { + return RecordQuery.newBuilder(StgProjectId.class, StgProject.class); } /** - * A {@code Column} which holds a {@code boolean} value. + * Columns of {@code StgProject} stored as record. */ - public static RecordColumn booleanColumn(String name) { - return create(name, Boolean.class, (r) -> booleanValue()); - } + @RecordColumns(ofType = StgProject.class) + public static final class StgProjectColumns { - /** - * The {@code boolean} value held by the corresponding {@linkplain #booleanColumn() entity - * column}. - */ - private static boolean booleanValue() { - return true; + private StgProjectColumns() { + } + + public static final RecordColumn name = + RecordColumn.create("name", String.class, StgProject::getName); + + public static final RecordColumn due_date = + RecordColumn.create("due_date", Timestamp.class, StgProject::getDueDate); + + public static final RecordColumn state_as_any = + RecordColumn.create("state_as_any", Any.class, AnyPacker::pack); + + public static final RecordColumn random_non_stored_column = + RecordColumn.create("random_non_stored_column", String.class, (p) -> "31415926"); + + /** + * Returns all the column definitions. + */ + public static Columns definitions() { + return Columns.of(name, due_date, state_as_any); + } } } From 537a1ddf85cc365e0ae440dc16c3566b9b89f9aa Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 30 Oct 2023 18:03:59 +0000 Subject: [PATCH 23/55] Make `updateBuilder` abstract, and leave it available just for SPI users. --- .../java/io/spine/server/entity/DefaultConverter.java | 5 +++++ .../java/io/spine/server/entity/StorageConverter.java | 9 ++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/server/src/main/java/io/spine/server/entity/DefaultConverter.java b/server/src/main/java/io/spine/server/entity/DefaultConverter.java index a5e13c7209..40a3cfb976 100644 --- a/server/src/main/java/io/spine/server/entity/DefaultConverter.java +++ b/server/src/main/java/io/spine/server/entity/DefaultConverter.java @@ -62,6 +62,11 @@ public StorageConverter withFieldMask(FieldMask fieldMask) { return new DefaultConverter<>(stateType, factory, fieldMask); } + @Override + protected void updateBuilder(EntityRecord.Builder builder, E entity) { + // Do nothing here. + } + /** * Injects the state into an entity. * diff --git a/server/src/main/java/io/spine/server/entity/StorageConverter.java b/server/src/main/java/io/spine/server/entity/StorageConverter.java index 19ed30d527..ea80e09dd8 100644 --- a/server/src/main/java/io/spine/server/entity/StorageConverter.java +++ b/server/src/main/java/io/spine/server/entity/StorageConverter.java @@ -151,20 +151,15 @@ protected E doBackward(EntityRecord entityRecord) { * Updates the builder with required values, if needed. * *

Derived classes may override to additionally tune - * the passed entity builder. - * - *

By default, this method does nothing. + * the passed entity record builder. * * @param builder * the entity builder to update * @param entity * the entity which data is passed to the {@link EntityRecord} we are building */ - // TODO:alex.tymchenko:2023-10-28: make abstract? Or kill? @SuppressWarnings({"WeakerAccess", "unused"}) - protected void updateBuilder(EntityRecord.Builder builder, E entity) { - // Do nothing by default. - } + protected abstract void updateBuilder(EntityRecord.Builder builder, E entity); /** * Derived classes must implement providing state injection into the passed entity. From 7bffd01d3d48f1730937c5fe305a2e260ae8c80f Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 30 Oct 2023 18:04:07 +0000 Subject: [PATCH 24/55] Update the reports. --- license-report.md | 324 +--------------------------------------------- pom.xml | 5 - 2 files changed, 6 insertions(+), 323 deletions(-) diff --git a/license-report.md b/license-report.md index 7dbf5bed77..b650ed8298 100644 --- a/license-report.md +++ b/license-report.md @@ -278,10 +278,6 @@ 1. **Group** : com.google.truth.extensions. **Name** : truth-proto-extension. **Version** : 1.1.5. * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.puppycrawl.tools. **Name** : checkstyle. **Version** : 10.3.4. - * **Project URL:** [https://checkstyle.org/](https://checkstyle.org/) - * **License:** [LGPL-2.1+](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt) - 1. **Group** : com.soywiz.korlibs.korte. **Name** : korte-jvm. **Version** : 2.7.0. * **Project URL:** [https://github.com/korlibs/korge-next](https://github.com/korlibs/korge-next) * **License:** [MIT](https://raw.githubusercontent.com/korlibs/korge-next/master/korge/LICENSE.txt) @@ -290,22 +286,6 @@ * **Project URL:** [http://github.com/square/javapoet/](http://github.com/square/javapoet/) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : commons-beanutils. **Name** : commons-beanutils. **Version** : 1.9.4. - * **Project URL:** [https://commons.apache.org/proper/commons-beanutils/](https://commons.apache.org/proper/commons-beanutils/) - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : commons-codec. **Name** : commons-codec. **Version** : 1.15. - * **Project URL:** [https://commons.apache.org/proper/commons-codec/](https://commons.apache.org/proper/commons-codec/) - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : commons-collections. **Name** : commons-collections. **Version** : 3.2.2. - * **Project URL:** [http://commons.apache.org/collections/](http://commons.apache.org/collections/) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : info.picocli. **Name** : picocli. **Version** : 4.6.3. - * **Project URL:** [http://picocli.info](http://picocli.info) - * **License:** [The Apache Software License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : io.github.davidburstrom.contester. **Name** : contester-breakpoint. **Version** : 0.2.0. * **Project URL:** [https://github.com/davidburstrom/contester](https://github.com/davidburstrom/contester) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -493,10 +473,6 @@ * **License:** [LGPL, version 2.1](http://www.gnu.org/licenses/licenses.html) 1. **Group** : net.ltgt.gradle. **Name** : gradle-errorprone-plugin. **Version** : 3.1.0.**No license information found** -1. **Group** : net.sf.saxon. **Name** : Saxon-HE. **Version** : 11.4. - * **Project URL:** [http://www.saxonica.com/](http://www.saxonica.com/) - * **License:** [Mozilla Public License Version 2.0](http://www.mozilla.org/MPL/2.0/) - 1. **Group** : net.sourceforge.pmd. **Name** : pmd-core. **Version** : 6.55.0. * **License:** [BSD-style](http://pmd.sourceforge.net/license.html) @@ -507,10 +483,6 @@ * **Project URL:** [http://saxon.sourceforge.net/](http://saxon.sourceforge.net/) * **License:** [Mozilla Public License Version 1.0](http://www.mozilla.org/MPL/MPL-1.0.txt) -1. **Group** : org.antlr. **Name** : antlr4-runtime. **Version** : 4.11.1. - * **Project URL:** [https://www.antlr.org/](https://www.antlr.org/) - * **License:** [BSD-3-Clause](https://www.antlr.org/license.html) - 1. **Group** : org.antlr. **Name** : antlr4-runtime. **Version** : 4.7.2. * **Project URL:** [http://www.antlr.org](http://www.antlr.org) * **License:** [The BSD License](http://www.antlr.org/license.html) @@ -519,15 +491,6 @@ * **Project URL:** [http://commons.apache.org/proper/commons-lang/](http://commons.apache.org/proper/commons-lang/) * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.apache.httpcomponents.client5. **Name** : httpclient5. **Version** : 5.1.3. - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : org.apache.httpcomponents.core5. **Name** : httpcore5. **Version** : 5.1.3. - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : org.apache.httpcomponents.core5. **Name** : httpcore5-h2. **Version** : 5.1.3. - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : org.apiguardian. **Name** : apiguardian-api. **Version** : 1.1.2. * **Project URL:** [https://github.com/apiguardian-team/apiguardian](https://github.com/apiguardian-team/apiguardian) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -574,12 +537,6 @@ 1. **Group** : org.jacoco. **Name** : org.jacoco.report. **Version** : 0.8.8. * **License:** [Eclipse Public License 2.0](https://www.eclipse.org/legal/epl-2.0/) -1. **Group** : org.javassist. **Name** : javassist. **Version** : 3.28.0-GA. - * **Project URL:** [http://www.javassist.org/](http://www.javassist.org/) - * **License:** [Apache License 2.0](http://www.apache.org/licenses/) - * **License:** [LGPL 2.1](http://www.gnu.org/licenses/lgpl-2.1.html) - * **License:** [MPL 1.1](http://www.mozilla.org/MPL/MPL-1.1.html) - 1. **Group** : org.jboss.forge.roaster. **Name** : roaster-api. **Version** : 2.28.0.Final. * **License:** [Eclipse Public License version 1.0](http://www.eclipse.org/legal/epl-v10.html) * **License:** [Public Domain](http://repository.jboss.org/licenses/cc0-1.0.txt) @@ -784,23 +741,14 @@ * **Project URL:** [https://github.com/hrldcpr/pcollections](https://github.com/hrldcpr/pcollections) * **License:** [The MIT License](https://opensource.org/licenses/mit-license.php) -1. **Group** : org.reflections. **Name** : reflections. **Version** : 0.10.2. - * **Project URL:** [http://github.com/ronmamo/reflections](http://github.com/ronmamo/reflections) - * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [WTFPL](http://www.wtfpl.net/) - 1. **Group** : org.snakeyaml. **Name** : snakeyaml-engine. **Version** : 2.6. * **Project URL:** [https://bitbucket.org/snakeyaml/snakeyaml-engine](https://bitbucket.org/snakeyaml/snakeyaml-engine) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.xmlresolver. **Name** : xmlresolver. **Version** : 4.4.3. - * **Project URL:** [https://github.com/xmlresolver/xmlresolver](https://github.com/xmlresolver/xmlresolver) - * **License:** [Apache License version 2.0](https://www.apache.org/licenses/LICENSE-2.0) - The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Oct 17 17:48:03 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 **Mon Oct 30 17:28:45 WET 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). @@ -1043,10 +991,6 @@ This report was generated on **Tue Oct 17 17:48:03 WEST 2023** using [Gradle-Lic 1. **Group** : com.google.truth.extensions. **Name** : truth-proto-extension. **Version** : 1.1.5. * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.puppycrawl.tools. **Name** : checkstyle. **Version** : 10.3.4. - * **Project URL:** [https://checkstyle.org/](https://checkstyle.org/) - * **License:** [LGPL-2.1+](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt) - 1. **Group** : com.soywiz.korlibs.korte. **Name** : korte-jvm. **Version** : 2.7.0. * **Project URL:** [https://github.com/korlibs/korge-next](https://github.com/korlibs/korge-next) * **License:** [MIT](https://raw.githubusercontent.com/korlibs/korge-next/master/korge/LICENSE.txt) @@ -1055,22 +999,6 @@ This report was generated on **Tue Oct 17 17:48:03 WEST 2023** using [Gradle-Lic * **Project URL:** [http://github.com/square/javapoet/](http://github.com/square/javapoet/) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : commons-beanutils. **Name** : commons-beanutils. **Version** : 1.9.4. - * **Project URL:** [https://commons.apache.org/proper/commons-beanutils/](https://commons.apache.org/proper/commons-beanutils/) - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : commons-codec. **Name** : commons-codec. **Version** : 1.15. - * **Project URL:** [https://commons.apache.org/proper/commons-codec/](https://commons.apache.org/proper/commons-codec/) - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : commons-collections. **Name** : commons-collections. **Version** : 3.2.2. - * **Project URL:** [http://commons.apache.org/collections/](http://commons.apache.org/collections/) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : info.picocli. **Name** : picocli. **Version** : 4.6.3. - * **Project URL:** [http://picocli.info](http://picocli.info) - * **License:** [The Apache Software License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : io.github.davidburstrom.contester. **Name** : contester-breakpoint. **Version** : 0.2.0. * **Project URL:** [https://github.com/davidburstrom/contester](https://github.com/davidburstrom/contester) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -1258,10 +1186,6 @@ This report was generated on **Tue Oct 17 17:48:03 WEST 2023** using [Gradle-Lic * **License:** [LGPL, version 2.1](http://www.gnu.org/licenses/licenses.html) 1. **Group** : net.ltgt.gradle. **Name** : gradle-errorprone-plugin. **Version** : 3.1.0.**No license information found** -1. **Group** : net.sf.saxon. **Name** : Saxon-HE. **Version** : 11.4. - * **Project URL:** [http://www.saxonica.com/](http://www.saxonica.com/) - * **License:** [Mozilla Public License Version 2.0](http://www.mozilla.org/MPL/2.0/) - 1. **Group** : net.sourceforge.pmd. **Name** : pmd-core. **Version** : 6.55.0. * **License:** [BSD-style](http://pmd.sourceforge.net/license.html) @@ -1272,10 +1196,6 @@ This report was generated on **Tue Oct 17 17:48:03 WEST 2023** using [Gradle-Lic * **Project URL:** [http://saxon.sourceforge.net/](http://saxon.sourceforge.net/) * **License:** [Mozilla Public License Version 1.0](http://www.mozilla.org/MPL/MPL-1.0.txt) -1. **Group** : org.antlr. **Name** : antlr4-runtime. **Version** : 4.11.1. - * **Project URL:** [https://www.antlr.org/](https://www.antlr.org/) - * **License:** [BSD-3-Clause](https://www.antlr.org/license.html) - 1. **Group** : org.antlr. **Name** : antlr4-runtime. **Version** : 4.7.2. * **Project URL:** [http://www.antlr.org](http://www.antlr.org) * **License:** [The BSD License](http://www.antlr.org/license.html) @@ -1284,15 +1204,6 @@ This report was generated on **Tue Oct 17 17:48:03 WEST 2023** using [Gradle-Lic * **Project URL:** [http://commons.apache.org/proper/commons-lang/](http://commons.apache.org/proper/commons-lang/) * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.apache.httpcomponents.client5. **Name** : httpclient5. **Version** : 5.1.3. - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : org.apache.httpcomponents.core5. **Name** : httpcore5. **Version** : 5.1.3. - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : org.apache.httpcomponents.core5. **Name** : httpcore5-h2. **Version** : 5.1.3. - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : org.apiguardian. **Name** : apiguardian-api. **Version** : 1.1.2. * **Project URL:** [https://github.com/apiguardian-team/apiguardian](https://github.com/apiguardian-team/apiguardian) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -1339,12 +1250,6 @@ This report was generated on **Tue Oct 17 17:48:03 WEST 2023** using [Gradle-Lic 1. **Group** : org.jacoco. **Name** : org.jacoco.report. **Version** : 0.8.8. * **License:** [Eclipse Public License 2.0](https://www.eclipse.org/legal/epl-2.0/) -1. **Group** : org.javassist. **Name** : javassist. **Version** : 3.28.0-GA. - * **Project URL:** [http://www.javassist.org/](http://www.javassist.org/) - * **License:** [Apache License 2.0](http://www.apache.org/licenses/) - * **License:** [LGPL 2.1](http://www.gnu.org/licenses/lgpl-2.1.html) - * **License:** [MPL 1.1](http://www.mozilla.org/MPL/MPL-1.1.html) - 1. **Group** : org.jboss.forge.roaster. **Name** : roaster-api. **Version** : 2.28.0.Final. * **License:** [Eclipse Public License version 1.0](http://www.eclipse.org/legal/epl-v10.html) * **License:** [Public Domain](http://repository.jboss.org/licenses/cc0-1.0.txt) @@ -1549,23 +1454,14 @@ This report was generated on **Tue Oct 17 17:48:03 WEST 2023** using [Gradle-Lic * **Project URL:** [https://github.com/hrldcpr/pcollections](https://github.com/hrldcpr/pcollections) * **License:** [The MIT License](https://opensource.org/licenses/mit-license.php) -1. **Group** : org.reflections. **Name** : reflections. **Version** : 0.10.2. - * **Project URL:** [http://github.com/ronmamo/reflections](http://github.com/ronmamo/reflections) - * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [WTFPL](http://www.wtfpl.net/) - 1. **Group** : org.snakeyaml. **Name** : snakeyaml-engine. **Version** : 2.6. * **Project URL:** [https://bitbucket.org/snakeyaml/snakeyaml-engine](https://bitbucket.org/snakeyaml/snakeyaml-engine) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.xmlresolver. **Name** : xmlresolver. **Version** : 4.4.3. - * **Project URL:** [https://github.com/xmlresolver/xmlresolver](https://github.com/xmlresolver/xmlresolver) - * **License:** [Apache License version 2.0](https://www.apache.org/licenses/LICENSE-2.0) - The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Oct 17 17:48:04 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 **Mon Oct 30 17:28:45 WET 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). @@ -1852,10 +1748,6 @@ This report was generated on **Tue Oct 17 17:48:04 WEST 2023** using [Gradle-Lic 1. **Group** : com.google.truth.extensions. **Name** : truth-proto-extension. **Version** : 1.1.5. * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.puppycrawl.tools. **Name** : checkstyle. **Version** : 10.3.4. - * **Project URL:** [https://checkstyle.org/](https://checkstyle.org/) - * **License:** [LGPL-2.1+](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt) - 1. **Group** : com.soywiz.korlibs.korte. **Name** : korte-jvm. **Version** : 2.7.0. * **Project URL:** [https://github.com/korlibs/korge-next](https://github.com/korlibs/korge-next) * **License:** [MIT](https://raw.githubusercontent.com/korlibs/korge-next/master/korge/LICENSE.txt) @@ -1864,22 +1756,6 @@ This report was generated on **Tue Oct 17 17:48:04 WEST 2023** using [Gradle-Lic * **Project URL:** [http://github.com/square/javapoet/](http://github.com/square/javapoet/) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : commons-beanutils. **Name** : commons-beanutils. **Version** : 1.9.4. - * **Project URL:** [https://commons.apache.org/proper/commons-beanutils/](https://commons.apache.org/proper/commons-beanutils/) - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : commons-codec. **Name** : commons-codec. **Version** : 1.15. - * **Project URL:** [https://commons.apache.org/proper/commons-codec/](https://commons.apache.org/proper/commons-codec/) - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : commons-collections. **Name** : commons-collections. **Version** : 3.2.2. - * **Project URL:** [http://commons.apache.org/collections/](http://commons.apache.org/collections/) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : info.picocli. **Name** : picocli. **Version** : 4.6.3. - * **Project URL:** [http://picocli.info](http://picocli.info) - * **License:** [The Apache Software License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : io.github.davidburstrom.contester. **Name** : contester-breakpoint. **Version** : 0.2.0. * **Project URL:** [https://github.com/davidburstrom/contester](https://github.com/davidburstrom/contester) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -2071,10 +1947,6 @@ This report was generated on **Tue Oct 17 17:48:04 WEST 2023** using [Gradle-Lic * **License:** [LGPL, version 2.1](http://www.gnu.org/licenses/licenses.html) 1. **Group** : net.ltgt.gradle. **Name** : gradle-errorprone-plugin. **Version** : 3.1.0.**No license information found** -1. **Group** : net.sf.saxon. **Name** : Saxon-HE. **Version** : 11.4. - * **Project URL:** [http://www.saxonica.com/](http://www.saxonica.com/) - * **License:** [Mozilla Public License Version 2.0](http://www.mozilla.org/MPL/2.0/) - 1. **Group** : net.sourceforge.pmd. **Name** : pmd-core. **Version** : 6.55.0. * **License:** [BSD-style](http://pmd.sourceforge.net/license.html) @@ -2085,10 +1957,6 @@ This report was generated on **Tue Oct 17 17:48:04 WEST 2023** using [Gradle-Lic * **Project URL:** [http://saxon.sourceforge.net/](http://saxon.sourceforge.net/) * **License:** [Mozilla Public License Version 1.0](http://www.mozilla.org/MPL/MPL-1.0.txt) -1. **Group** : org.antlr. **Name** : antlr4-runtime. **Version** : 4.11.1. - * **Project URL:** [https://www.antlr.org/](https://www.antlr.org/) - * **License:** [BSD-3-Clause](https://www.antlr.org/license.html) - 1. **Group** : org.antlr. **Name** : antlr4-runtime. **Version** : 4.7.2. * **Project URL:** [http://www.antlr.org](http://www.antlr.org) * **License:** [The BSD License](http://www.antlr.org/license.html) @@ -2097,15 +1965,6 @@ This report was generated on **Tue Oct 17 17:48:04 WEST 2023** using [Gradle-Lic * **Project URL:** [http://commons.apache.org/proper/commons-lang/](http://commons.apache.org/proper/commons-lang/) * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.apache.httpcomponents.client5. **Name** : httpclient5. **Version** : 5.1.3. - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : org.apache.httpcomponents.core5. **Name** : httpcore5. **Version** : 5.1.3. - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : org.apache.httpcomponents.core5. **Name** : httpcore5-h2. **Version** : 5.1.3. - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : org.apiguardian. **Name** : apiguardian-api. **Version** : 1.1.2. * **Project URL:** [https://github.com/apiguardian-team/apiguardian](https://github.com/apiguardian-team/apiguardian) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -2152,12 +2011,6 @@ This report was generated on **Tue Oct 17 17:48:04 WEST 2023** using [Gradle-Lic 1. **Group** : org.jacoco. **Name** : org.jacoco.report. **Version** : 0.8.8. * **License:** [Eclipse Public License 2.0](https://www.eclipse.org/legal/epl-2.0/) -1. **Group** : org.javassist. **Name** : javassist. **Version** : 3.28.0-GA. - * **Project URL:** [http://www.javassist.org/](http://www.javassist.org/) - * **License:** [Apache License 2.0](http://www.apache.org/licenses/) - * **License:** [LGPL 2.1](http://www.gnu.org/licenses/lgpl-2.1.html) - * **License:** [MPL 1.1](http://www.mozilla.org/MPL/MPL-1.1.html) - 1. **Group** : org.jboss.forge.roaster. **Name** : roaster-api. **Version** : 2.28.0.Final. * **License:** [Eclipse Public License version 1.0](http://www.eclipse.org/legal/epl-v10.html) * **License:** [Public Domain](http://repository.jboss.org/licenses/cc0-1.0.txt) @@ -2362,23 +2215,14 @@ This report was generated on **Tue Oct 17 17:48:04 WEST 2023** using [Gradle-Lic * **Project URL:** [https://github.com/hrldcpr/pcollections](https://github.com/hrldcpr/pcollections) * **License:** [The MIT License](https://opensource.org/licenses/mit-license.php) -1. **Group** : org.reflections. **Name** : reflections. **Version** : 0.10.2. - * **Project URL:** [http://github.com/ronmamo/reflections](http://github.com/ronmamo/reflections) - * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [WTFPL](http://www.wtfpl.net/) - 1. **Group** : org.snakeyaml. **Name** : snakeyaml-engine. **Version** : 2.6. * **Project URL:** [https://bitbucket.org/snakeyaml/snakeyaml-engine](https://bitbucket.org/snakeyaml/snakeyaml-engine) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.xmlresolver. **Name** : xmlresolver. **Version** : 4.4.3. - * **Project URL:** [https://github.com/xmlresolver/xmlresolver](https://github.com/xmlresolver/xmlresolver) - * **License:** [Apache License version 2.0](https://www.apache.org/licenses/LICENSE-2.0) - The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Oct 17 17:48:04 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 **Mon Oct 30 17:28:46 WET 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). @@ -2774,10 +2618,6 @@ This report was generated on **Tue Oct 17 17:48:04 WEST 2023** using [Gradle-Lic 1. **Group** : com.google.truth.extensions. **Name** : truth-proto-extension. **Version** : 1.1.5. * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.puppycrawl.tools. **Name** : checkstyle. **Version** : 10.3.4. - * **Project URL:** [https://checkstyle.org/](https://checkstyle.org/) - * **License:** [LGPL-2.1+](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt) - 1. **Group** : com.soywiz.korlibs.korte. **Name** : korte-jvm. **Version** : 2.7.0. * **Project URL:** [https://github.com/korlibs/korge-next](https://github.com/korlibs/korge-next) * **License:** [MIT](https://raw.githubusercontent.com/korlibs/korge-next/master/korge/LICENSE.txt) @@ -2786,22 +2626,6 @@ This report was generated on **Tue Oct 17 17:48:04 WEST 2023** using [Gradle-Lic * **Project URL:** [http://github.com/square/javapoet/](http://github.com/square/javapoet/) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : commons-beanutils. **Name** : commons-beanutils. **Version** : 1.9.4. - * **Project URL:** [https://commons.apache.org/proper/commons-beanutils/](https://commons.apache.org/proper/commons-beanutils/) - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : commons-codec. **Name** : commons-codec. **Version** : 1.15. - * **Project URL:** [https://commons.apache.org/proper/commons-codec/](https://commons.apache.org/proper/commons-codec/) - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : commons-collections. **Name** : commons-collections. **Version** : 3.2.2. - * **Project URL:** [http://commons.apache.org/collections/](http://commons.apache.org/collections/) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : info.picocli. **Name** : picocli. **Version** : 4.6.3. - * **Project URL:** [http://picocli.info](http://picocli.info) - * **License:** [The Apache Software License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : io.github.davidburstrom.contester. **Name** : contester-breakpoint. **Version** : 0.2.0. * **Project URL:** [https://github.com/davidburstrom/contester](https://github.com/davidburstrom/contester) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -3001,10 +2825,6 @@ This report was generated on **Tue Oct 17 17:48:04 WEST 2023** using [Gradle-Lic * **License:** [LGPL, version 2.1](http://www.gnu.org/licenses/licenses.html) 1. **Group** : net.ltgt.gradle. **Name** : gradle-errorprone-plugin. **Version** : 3.1.0.**No license information found** -1. **Group** : net.sf.saxon. **Name** : Saxon-HE. **Version** : 11.4. - * **Project URL:** [http://www.saxonica.com/](http://www.saxonica.com/) - * **License:** [Mozilla Public License Version 2.0](http://www.mozilla.org/MPL/2.0/) - 1. **Group** : net.sourceforge.pmd. **Name** : pmd-core. **Version** : 6.55.0. * **License:** [BSD-style](http://pmd.sourceforge.net/license.html) @@ -3015,10 +2835,6 @@ This report was generated on **Tue Oct 17 17:48:04 WEST 2023** using [Gradle-Lic * **Project URL:** [http://saxon.sourceforge.net/](http://saxon.sourceforge.net/) * **License:** [Mozilla Public License Version 1.0](http://www.mozilla.org/MPL/MPL-1.0.txt) -1. **Group** : org.antlr. **Name** : antlr4-runtime. **Version** : 4.11.1. - * **Project URL:** [https://www.antlr.org/](https://www.antlr.org/) - * **License:** [BSD-3-Clause](https://www.antlr.org/license.html) - 1. **Group** : org.antlr. **Name** : antlr4-runtime. **Version** : 4.7.2. * **Project URL:** [http://www.antlr.org](http://www.antlr.org) * **License:** [The BSD License](http://www.antlr.org/license.html) @@ -3027,15 +2843,6 @@ This report was generated on **Tue Oct 17 17:48:04 WEST 2023** using [Gradle-Lic * **Project URL:** [http://commons.apache.org/proper/commons-lang/](http://commons.apache.org/proper/commons-lang/) * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.apache.httpcomponents.client5. **Name** : httpclient5. **Version** : 5.1.3. - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : org.apache.httpcomponents.core5. **Name** : httpcore5. **Version** : 5.1.3. - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : org.apache.httpcomponents.core5. **Name** : httpcore5-h2. **Version** : 5.1.3. - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : org.apiguardian. **Name** : apiguardian-api. **Version** : 1.1.2. * **Project URL:** [https://github.com/apiguardian-team/apiguardian](https://github.com/apiguardian-team/apiguardian) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -3082,12 +2889,6 @@ This report was generated on **Tue Oct 17 17:48:04 WEST 2023** using [Gradle-Lic 1. **Group** : org.jacoco. **Name** : org.jacoco.report. **Version** : 0.8.8. * **License:** [Eclipse Public License 2.0](https://www.eclipse.org/legal/epl-2.0/) -1. **Group** : org.javassist. **Name** : javassist. **Version** : 3.28.0-GA. - * **Project URL:** [http://www.javassist.org/](http://www.javassist.org/) - * **License:** [Apache License 2.0](http://www.apache.org/licenses/) - * **License:** [LGPL 2.1](http://www.gnu.org/licenses/lgpl-2.1.html) - * **License:** [MPL 1.1](http://www.mozilla.org/MPL/MPL-1.1.html) - 1. **Group** : org.jboss.forge.roaster. **Name** : roaster-api. **Version** : 2.28.0.Final. * **License:** [Eclipse Public License version 1.0](http://www.eclipse.org/legal/epl-v10.html) * **License:** [Public Domain](http://repository.jboss.org/licenses/cc0-1.0.txt) @@ -3295,23 +3096,14 @@ This report was generated on **Tue Oct 17 17:48:04 WEST 2023** using [Gradle-Lic * **Project URL:** [https://github.com/hrldcpr/pcollections](https://github.com/hrldcpr/pcollections) * **License:** [The MIT License](https://opensource.org/licenses/mit-license.php) -1. **Group** : org.reflections. **Name** : reflections. **Version** : 0.10.2. - * **Project URL:** [http://github.com/ronmamo/reflections](http://github.com/ronmamo/reflections) - * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [WTFPL](http://www.wtfpl.net/) - 1. **Group** : org.snakeyaml. **Name** : snakeyaml-engine. **Version** : 2.6. * **Project URL:** [https://bitbucket.org/snakeyaml/snakeyaml-engine](https://bitbucket.org/snakeyaml/snakeyaml-engine) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.xmlresolver. **Name** : xmlresolver. **Version** : 4.4.3. - * **Project URL:** [https://github.com/xmlresolver/xmlresolver](https://github.com/xmlresolver/xmlresolver) - * **License:** [Apache License version 2.0](https://www.apache.org/licenses/LICENSE-2.0) - The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Oct 17 17:48:05 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 **Mon Oct 30 17:28:46 WET 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). @@ -3707,10 +3499,6 @@ This report was generated on **Tue Oct 17 17:48:05 WEST 2023** using [Gradle-Lic 1. **Group** : com.google.truth.extensions. **Name** : truth-proto-extension. **Version** : 1.1.5. * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.puppycrawl.tools. **Name** : checkstyle. **Version** : 10.3.4. - * **Project URL:** [https://checkstyle.org/](https://checkstyle.org/) - * **License:** [LGPL-2.1+](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt) - 1. **Group** : com.soywiz.korlibs.korte. **Name** : korte-jvm. **Version** : 2.7.0. * **Project URL:** [https://github.com/korlibs/korge-next](https://github.com/korlibs/korge-next) * **License:** [MIT](https://raw.githubusercontent.com/korlibs/korge-next/master/korge/LICENSE.txt) @@ -3719,22 +3507,6 @@ This report was generated on **Tue Oct 17 17:48:05 WEST 2023** using [Gradle-Lic * **Project URL:** [http://github.com/square/javapoet/](http://github.com/square/javapoet/) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : commons-beanutils. **Name** : commons-beanutils. **Version** : 1.9.4. - * **Project URL:** [https://commons.apache.org/proper/commons-beanutils/](https://commons.apache.org/proper/commons-beanutils/) - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : commons-codec. **Name** : commons-codec. **Version** : 1.15. - * **Project URL:** [https://commons.apache.org/proper/commons-codec/](https://commons.apache.org/proper/commons-codec/) - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : commons-collections. **Name** : commons-collections. **Version** : 3.2.2. - * **Project URL:** [http://commons.apache.org/collections/](http://commons.apache.org/collections/) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : info.picocli. **Name** : picocli. **Version** : 4.6.3. - * **Project URL:** [http://picocli.info](http://picocli.info) - * **License:** [The Apache Software License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : io.github.davidburstrom.contester. **Name** : contester-breakpoint. **Version** : 0.2.0. * **Project URL:** [https://github.com/davidburstrom/contester](https://github.com/davidburstrom/contester) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -3934,10 +3706,6 @@ This report was generated on **Tue Oct 17 17:48:05 WEST 2023** using [Gradle-Lic * **License:** [LGPL, version 2.1](http://www.gnu.org/licenses/licenses.html) 1. **Group** : net.ltgt.gradle. **Name** : gradle-errorprone-plugin. **Version** : 3.1.0.**No license information found** -1. **Group** : net.sf.saxon. **Name** : Saxon-HE. **Version** : 11.4. - * **Project URL:** [http://www.saxonica.com/](http://www.saxonica.com/) - * **License:** [Mozilla Public License Version 2.0](http://www.mozilla.org/MPL/2.0/) - 1. **Group** : net.sourceforge.pmd. **Name** : pmd-core. **Version** : 6.55.0. * **License:** [BSD-style](http://pmd.sourceforge.net/license.html) @@ -3948,10 +3716,6 @@ This report was generated on **Tue Oct 17 17:48:05 WEST 2023** using [Gradle-Lic * **Project URL:** [http://saxon.sourceforge.net/](http://saxon.sourceforge.net/) * **License:** [Mozilla Public License Version 1.0](http://www.mozilla.org/MPL/MPL-1.0.txt) -1. **Group** : org.antlr. **Name** : antlr4-runtime. **Version** : 4.11.1. - * **Project URL:** [https://www.antlr.org/](https://www.antlr.org/) - * **License:** [BSD-3-Clause](https://www.antlr.org/license.html) - 1. **Group** : org.antlr. **Name** : antlr4-runtime. **Version** : 4.7.2. * **Project URL:** [http://www.antlr.org](http://www.antlr.org) * **License:** [The BSD License](http://www.antlr.org/license.html) @@ -3960,15 +3724,6 @@ This report was generated on **Tue Oct 17 17:48:05 WEST 2023** using [Gradle-Lic * **Project URL:** [http://commons.apache.org/proper/commons-lang/](http://commons.apache.org/proper/commons-lang/) * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.apache.httpcomponents.client5. **Name** : httpclient5. **Version** : 5.1.3. - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : org.apache.httpcomponents.core5. **Name** : httpcore5. **Version** : 5.1.3. - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : org.apache.httpcomponents.core5. **Name** : httpcore5-h2. **Version** : 5.1.3. - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : org.apiguardian. **Name** : apiguardian-api. **Version** : 1.1.2. * **Project URL:** [https://github.com/apiguardian-team/apiguardian](https://github.com/apiguardian-team/apiguardian) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -4015,12 +3770,6 @@ This report was generated on **Tue Oct 17 17:48:05 WEST 2023** using [Gradle-Lic 1. **Group** : org.jacoco. **Name** : org.jacoco.report. **Version** : 0.8.8. * **License:** [Eclipse Public License 2.0](https://www.eclipse.org/legal/epl-2.0/) -1. **Group** : org.javassist. **Name** : javassist. **Version** : 3.28.0-GA. - * **Project URL:** [http://www.javassist.org/](http://www.javassist.org/) - * **License:** [Apache License 2.0](http://www.apache.org/licenses/) - * **License:** [LGPL 2.1](http://www.gnu.org/licenses/lgpl-2.1.html) - * **License:** [MPL 1.1](http://www.mozilla.org/MPL/MPL-1.1.html) - 1. **Group** : org.jboss.forge.roaster. **Name** : roaster-api. **Version** : 2.28.0.Final. * **License:** [Eclipse Public License version 1.0](http://www.eclipse.org/legal/epl-v10.html) * **License:** [Public Domain](http://repository.jboss.org/licenses/cc0-1.0.txt) @@ -4228,23 +3977,14 @@ This report was generated on **Tue Oct 17 17:48:05 WEST 2023** using [Gradle-Lic * **Project URL:** [https://github.com/hrldcpr/pcollections](https://github.com/hrldcpr/pcollections) * **License:** [The MIT License](https://opensource.org/licenses/mit-license.php) -1. **Group** : org.reflections. **Name** : reflections. **Version** : 0.10.2. - * **Project URL:** [http://github.com/ronmamo/reflections](http://github.com/ronmamo/reflections) - * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [WTFPL](http://www.wtfpl.net/) - 1. **Group** : org.snakeyaml. **Name** : snakeyaml-engine. **Version** : 2.6. * **Project URL:** [https://bitbucket.org/snakeyaml/snakeyaml-engine](https://bitbucket.org/snakeyaml/snakeyaml-engine) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.xmlresolver. **Name** : xmlresolver. **Version** : 4.4.3. - * **Project URL:** [https://github.com/xmlresolver/xmlresolver](https://github.com/xmlresolver/xmlresolver) - * **License:** [Apache License version 2.0](https://www.apache.org/licenses/LICENSE-2.0) - The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Oct 17 17:48:05 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 **Mon Oct 30 17:28:47 WET 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). @@ -4640,10 +4380,6 @@ This report was generated on **Tue Oct 17 17:48:05 WEST 2023** using [Gradle-Lic 1. **Group** : com.google.truth.extensions. **Name** : truth-proto-extension. **Version** : 1.1.5. * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.puppycrawl.tools. **Name** : checkstyle. **Version** : 10.3.4. - * **Project URL:** [https://checkstyle.org/](https://checkstyle.org/) - * **License:** [LGPL-2.1+](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt) - 1. **Group** : com.soywiz.korlibs.korte. **Name** : korte-jvm. **Version** : 2.7.0. * **Project URL:** [https://github.com/korlibs/korge-next](https://github.com/korlibs/korge-next) * **License:** [MIT](https://raw.githubusercontent.com/korlibs/korge-next/master/korge/LICENSE.txt) @@ -4652,22 +4388,6 @@ This report was generated on **Tue Oct 17 17:48:05 WEST 2023** using [Gradle-Lic * **Project URL:** [http://github.com/square/javapoet/](http://github.com/square/javapoet/) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : commons-beanutils. **Name** : commons-beanutils. **Version** : 1.9.4. - * **Project URL:** [https://commons.apache.org/proper/commons-beanutils/](https://commons.apache.org/proper/commons-beanutils/) - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : commons-codec. **Name** : commons-codec. **Version** : 1.15. - * **Project URL:** [https://commons.apache.org/proper/commons-codec/](https://commons.apache.org/proper/commons-codec/) - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : commons-collections. **Name** : commons-collections. **Version** : 3.2.2. - * **Project URL:** [http://commons.apache.org/collections/](http://commons.apache.org/collections/) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : info.picocli. **Name** : picocli. **Version** : 4.6.3. - * **Project URL:** [http://picocli.info](http://picocli.info) - * **License:** [The Apache Software License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : io.github.davidburstrom.contester. **Name** : contester-breakpoint. **Version** : 0.2.0. * **Project URL:** [https://github.com/davidburstrom/contester](https://github.com/davidburstrom/contester) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -4915,10 +4635,6 @@ This report was generated on **Tue Oct 17 17:48:05 WEST 2023** using [Gradle-Lic * **License:** [LGPL, version 2.1](http://www.gnu.org/licenses/licenses.html) 1. **Group** : net.ltgt.gradle. **Name** : gradle-errorprone-plugin. **Version** : 3.1.0.**No license information found** -1. **Group** : net.sf.saxon. **Name** : Saxon-HE. **Version** : 11.4. - * **Project URL:** [http://www.saxonica.com/](http://www.saxonica.com/) - * **License:** [Mozilla Public License Version 2.0](http://www.mozilla.org/MPL/2.0/) - 1. **Group** : net.sourceforge.pmd. **Name** : pmd-core. **Version** : 6.55.0. * **License:** [BSD-style](http://pmd.sourceforge.net/license.html) @@ -4929,10 +4645,6 @@ This report was generated on **Tue Oct 17 17:48:05 WEST 2023** using [Gradle-Lic * **Project URL:** [http://saxon.sourceforge.net/](http://saxon.sourceforge.net/) * **License:** [Mozilla Public License Version 1.0](http://www.mozilla.org/MPL/MPL-1.0.txt) -1. **Group** : org.antlr. **Name** : antlr4-runtime. **Version** : 4.11.1. - * **Project URL:** [https://www.antlr.org/](https://www.antlr.org/) - * **License:** [BSD-3-Clause](https://www.antlr.org/license.html) - 1. **Group** : org.antlr. **Name** : antlr4-runtime. **Version** : 4.7.2. * **Project URL:** [http://www.antlr.org](http://www.antlr.org) * **License:** [The BSD License](http://www.antlr.org/license.html) @@ -4941,15 +4653,6 @@ This report was generated on **Tue Oct 17 17:48:05 WEST 2023** using [Gradle-Lic * **Project URL:** [http://commons.apache.org/proper/commons-lang/](http://commons.apache.org/proper/commons-lang/) * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.apache.httpcomponents.client5. **Name** : httpclient5. **Version** : 5.1.3. - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : org.apache.httpcomponents.core5. **Name** : httpcore5. **Version** : 5.1.3. - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : org.apache.httpcomponents.core5. **Name** : httpcore5-h2. **Version** : 5.1.3. - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : org.apiguardian. **Name** : apiguardian-api. **Version** : 1.1.2. * **Project URL:** [https://github.com/apiguardian-team/apiguardian](https://github.com/apiguardian-team/apiguardian) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -4996,12 +4699,6 @@ This report was generated on **Tue Oct 17 17:48:05 WEST 2023** using [Gradle-Lic 1. **Group** : org.jacoco. **Name** : org.jacoco.report. **Version** : 0.8.8. * **License:** [Eclipse Public License 2.0](https://www.eclipse.org/legal/epl-2.0/) -1. **Group** : org.javassist. **Name** : javassist. **Version** : 3.28.0-GA. - * **Project URL:** [http://www.javassist.org/](http://www.javassist.org/) - * **License:** [Apache License 2.0](http://www.apache.org/licenses/) - * **License:** [LGPL 2.1](http://www.gnu.org/licenses/lgpl-2.1.html) - * **License:** [MPL 1.1](http://www.mozilla.org/MPL/MPL-1.1.html) - 1. **Group** : org.jboss.forge.roaster. **Name** : roaster-api. **Version** : 2.28.0.Final. * **License:** [Eclipse Public License version 1.0](http://www.eclipse.org/legal/epl-v10.html) * **License:** [Public Domain](http://repository.jboss.org/licenses/cc0-1.0.txt) @@ -5209,20 +4906,11 @@ This report was generated on **Tue Oct 17 17:48:05 WEST 2023** using [Gradle-Lic * **Project URL:** [https://github.com/hrldcpr/pcollections](https://github.com/hrldcpr/pcollections) * **License:** [The MIT License](https://opensource.org/licenses/mit-license.php) -1. **Group** : org.reflections. **Name** : reflections. **Version** : 0.10.2. - * **Project URL:** [http://github.com/ronmamo/reflections](http://github.com/ronmamo/reflections) - * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [WTFPL](http://www.wtfpl.net/) - 1. **Group** : org.snakeyaml. **Name** : snakeyaml-engine. **Version** : 2.6. * **Project URL:** [https://bitbucket.org/snakeyaml/snakeyaml-engine](https://bitbucket.org/snakeyaml/snakeyaml-engine) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.xmlresolver. **Name** : xmlresolver. **Version** : 4.4.3. - * **Project URL:** [https://github.com/xmlresolver/xmlresolver](https://github.com/xmlresolver/xmlresolver) - * **License:** [Apache License version 2.0](https://www.apache.org/licenses/LICENSE-2.0) - The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Oct 17 17:48:06 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 **Mon Oct 30 17:28:47 WET 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 c044183239..4317952246 100644 --- a/pom.xml +++ b/pom.xml @@ -188,11 +188,6 @@ all modules and does not describe the project structure per-subproject. protoc 3.24.1 - - com.puppycrawl.tools - checkstyle - 10.3.4 - io.gitlab.arturbosch.detekt detekt-cli From 8156a8b98be22b0d85adaf216c13b976a6da38b9 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 30 Oct 2023 18:23:15 +0000 Subject: [PATCH 25/55] Move the columns of `StgProject` to `GivenStorageProject` and update naming. Introduce another set of API endpoints in `GivenStorageProject` which treat it as an Entity state. --- .../storage/given/GivenStorageProject.java | 71 +++++++++++++++++++ .../memory/RecordQueryMatcherTest.java | 28 +++++--- .../given/RecordQueryMatcherTestEnv.java | 52 -------------- 3 files changed, 88 insertions(+), 63 deletions(-) diff --git a/server/src/test/java/io/spine/server/storage/given/GivenStorageProject.java b/server/src/test/java/io/spine/server/storage/given/GivenStorageProject.java index d5a1c2afbc..3996df4097 100644 --- a/server/src/test/java/io/spine/server/storage/given/GivenStorageProject.java +++ b/server/src/test/java/io/spine/server/storage/given/GivenStorageProject.java @@ -26,13 +26,24 @@ package io.spine.server.storage.given; +import com.google.protobuf.Any; import com.google.protobuf.Timestamp; import com.google.protobuf.util.Durations; import com.google.protobuf.util.Timestamps; +import io.spine.base.Identifier; +import io.spine.protobuf.AnyPacker; +import io.spine.query.Columns; +import io.spine.query.RecordColumn; +import io.spine.query.RecordColumns; +import io.spine.server.entity.EntityRecord; +import io.spine.server.entity.storage.SpecScanner; +import io.spine.server.storage.MessageRecordSpec; +import io.spine.server.storage.RecordWithColumns; import io.spine.test.storage.StgProject; import io.spine.test.storage.StgProject.Status; import io.spine.test.storage.StgProjectId; import io.spine.test.storage.StgTask; +import io.spine.testing.core.given.GivenVersion; import static io.spine.base.Identifier.newUuid; import static io.spine.base.Time.currentTime; @@ -41,6 +52,15 @@ public final class GivenStorageProject { + private static final MessageRecordSpec messageSpec = + new MessageRecordSpec<>(StgProjectId.class, + StgProject.class, + StgProject::getId, + StgProjectColumns.definitions()); + + private static final MessageRecordSpec entityRecordSpec = + SpecScanner.scan(StgProjectId.class, StgProject.class); + /** * Prevents this utility from instantiation. */ @@ -108,4 +128,55 @@ public static StgProject newState() { var id = newId(); return newState(id); } + + /** + * Returns the record specification for {@code StgProject}. + */ + public static MessageRecordSpec projectMessageSpec() { + return messageSpec; + } + + /** + * Generates a new {@code StgProject} as Entity, + * and wraps it into a {@code RecordWithColumns}. + */ + public static RecordWithColumns newEntityRecord() { + var project = newState(); + var record = EntityRecord.newBuilder() + .setEntityId(Identifier.pack(project.getId())) + .setVersion(GivenVersion.withNumber(7)) + .setState(AnyPacker.pack(project)) + .build(); + var result = RecordWithColumns.create(record, entityRecordSpec); + return result; + } + + /** + * Columns of {@code StgProject} stored as record. + */ + @RecordColumns(ofType = StgProject.class) + public static final class StgProjectColumns { + + private StgProjectColumns() { + } + + public static final RecordColumn name = + RecordColumn.create("name", String.class, StgProject::getName); + + public static final RecordColumn due_date = + RecordColumn.create("due_date", Timestamp.class, StgProject::getDueDate); + + public static final RecordColumn state_as_any = + RecordColumn.create("state_as_any", Any.class, AnyPacker::pack); + + public static final RecordColumn random_non_stored_column = + RecordColumn.create("random_non_stored_column", String.class, (p) -> "31415926"); + + /** + * Returns all the column definitions. + */ + public static Columns definitions() { + return Columns.of(name, due_date, state_as_any); + } + } } diff --git a/server/src/test/java/io/spine/server/storage/memory/RecordQueryMatcherTest.java b/server/src/test/java/io/spine/server/storage/memory/RecordQueryMatcherTest.java index 7441f6816c..9920c238d9 100644 --- a/server/src/test/java/io/spine/server/storage/memory/RecordQueryMatcherTest.java +++ b/server/src/test/java/io/spine/server/storage/memory/RecordQueryMatcherTest.java @@ -27,18 +27,19 @@ package io.spine.server.storage.memory; import io.spine.server.storage.RecordWithColumns; -import io.spine.server.storage.memory.given.RecordQueryMatcherTestEnv; -import io.spine.server.storage.memory.given.RecordQueryMatcherTestEnv.StgProjectColumns; +import io.spine.server.storage.given.GivenStorageProject.StgProjectColumns; +import io.spine.test.storage.StgProject; import io.spine.test.storage.StgProjectId; +import org.checkerframework.checker.nullness.qual.NonNull; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import static com.google.common.truth.Truth.assertThat; import static io.spine.protobuf.AnyPacker.pack; import static io.spine.server.storage.given.GivenStorageProject.newState; +import static io.spine.server.storage.given.GivenStorageProject.projectMessageSpec; import static io.spine.server.storage.memory.given.RecordQueryMatcherTestEnv.newBuilder; import static io.spine.server.storage.memory.given.RecordQueryMatcherTestEnv.recordSubject; -import static io.spine.server.storage.memory.given.RecordQueryMatcherTestEnv.projectSpec; import static io.spine.testdata.Sample.messageOfType; import static io.spine.testing.TestValues.nullRef; @@ -53,7 +54,7 @@ void matchEverythingToEmpty() { assertThat(matcher.test(nullRef())) .isFalse(); - assertThat(matcher.test(RecordWithColumns.create(newState(), projectSpec()))) + assertThat(matcher.test(asRecord(newState()))) .isTrue(); } @@ -67,8 +68,8 @@ void matchIds() { var matcher = new RecordQueryMatcher<>(subject); var matching = newState(matchingId); var nonMatching = newState(nonMatchingId); - var matchingRecord = RecordWithColumns.create(matching, projectSpec()); - var nonMatchingRecord = RecordWithColumns.create(nonMatching, projectSpec()); + var matchingRecord = asRecord(matching); + var nonMatchingRecord = asRecord(nonMatching); assertThat(matcher.test(matchingRecord)) .isTrue(); assertThat(matcher.test(nonMatchingRecord)) @@ -82,9 +83,9 @@ void matchColumns() { var matchingName = matchingState.getName(); var query = newBuilder().where(StgProjectColumns.name).is(matchingName).build(); var matcher = new RecordQueryMatcher<>(query.subject()); - var matchingRecord = RecordWithColumns.create(matchingState, projectSpec()); + var matchingRecord = asRecord(matchingState); var nonMatching = newState(); - var nonMatchingRecord = RecordWithColumns.create(nonMatching, projectSpec()); + var nonMatchingRecord = asRecord(nonMatching); assertThat(matcher.test(matchingRecord)) .isTrue(); @@ -92,13 +93,18 @@ void matchColumns() { .isFalse(); } + @NonNull + private static RecordWithColumns asRecord(StgProject state) { + return RecordWithColumns.create(state, projectMessageSpec()); + } + @Test @DisplayName("match `Any` instances") void matchAnyInstances() { var matchingState = newState(); var queryValue = pack(matchingState); - var matchingRecord = RecordWithColumns.create(matchingState, projectSpec()); - var nonMatchingRecord = RecordWithColumns.create(newState(), projectSpec()); + var matchingRecord = asRecord(matchingState); + var nonMatchingRecord = asRecord(newState()); var query = newBuilder() .where(StgProjectColumns.state_as_any).is(queryValue).build(); @@ -117,7 +123,7 @@ void notMatchByWrongField() { .build(); var matcher = new RecordQueryMatcher<>(query); - var recordWithColumns = RecordWithColumns.create(newState(), projectSpec()); + var recordWithColumns = asRecord(newState()); assertThat(matcher.test(recordWithColumns)) .isFalse(); } diff --git a/server/src/test/java/io/spine/server/storage/memory/given/RecordQueryMatcherTestEnv.java b/server/src/test/java/io/spine/server/storage/memory/given/RecordQueryMatcherTestEnv.java index 1e9e75f2e8..4ea3d610d0 100644 --- a/server/src/test/java/io/spine/server/storage/memory/given/RecordQueryMatcherTestEnv.java +++ b/server/src/test/java/io/spine/server/storage/memory/given/RecordQueryMatcherTestEnv.java @@ -26,45 +26,22 @@ package io.spine.server.storage.memory.given; -import com.google.protobuf.Any; -import com.google.protobuf.Timestamp; -import io.spine.protobuf.AnyPacker; -import io.spine.query.Columns; -import io.spine.query.RecordColumn; -import io.spine.query.RecordColumns; import io.spine.query.RecordQuery; import io.spine.query.RecordQueryBuilder; import io.spine.query.Subject; -import io.spine.server.storage.MessageRecordSpec; import io.spine.test.storage.StgProject; import io.spine.test.storage.StgProjectId; /** * The test environment for {@link io.spine.server.storage.memory.RecordQueryMatcher} tests. - * - *

Provides various types of {@linkplain RecordColumn record columns} - * that can be used to emulate a client-side query. */ @SuppressWarnings("BadImport") // `create` looks fine in this context. public final class RecordQueryMatcherTestEnv { - private static final MessageRecordSpec spec = - new MessageRecordSpec<>(StgProjectId.class, - StgProject.class, - StgProject::getId, - StgProjectColumns.definitions()); - /** Prevents instantiation of this test env class. */ private RecordQueryMatcherTestEnv() { } - /** - * Returns the record specification for {@code StgProject}. - */ - public static MessageRecordSpec projectSpec() { - return spec; - } - /** * Creates an empty {@code Subject} for the {@code StgProject}. */ @@ -95,33 +72,4 @@ private static Class parameterizedClsOf(I id) { public static RecordQueryBuilder newBuilder() { return RecordQuery.newBuilder(StgProjectId.class, StgProject.class); } - - /** - * Columns of {@code StgProject} stored as record. - */ - @RecordColumns(ofType = StgProject.class) - public static final class StgProjectColumns { - - private StgProjectColumns() { - } - - public static final RecordColumn name = - RecordColumn.create("name", String.class, StgProject::getName); - - public static final RecordColumn due_date = - RecordColumn.create("due_date", Timestamp.class, StgProject::getDueDate); - - public static final RecordColumn state_as_any = - RecordColumn.create("state_as_any", Any.class, AnyPacker::pack); - - public static final RecordColumn random_non_stored_column = - RecordColumn.create("random_non_stored_column", String.class, (p) -> "31415926"); - - /** - * Returns all the column definitions. - */ - public static Columns definitions() { - return Columns.of(name, due_date, state_as_any); - } - } } From 6c3043923638b4db4873f537cc433e86d90b5dc0 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 30 Oct 2023 18:23:55 +0000 Subject: [PATCH 26/55] Avoid using `EntityRecordWithColumns` in `EntityRecordStorageTest` and its test environment. --- .../storage/EntityRecordStorageTest.java | 9 ++-- .../given/EntityRecordStorageTestEnv.java | 42 ------------------- 2 files changed, 4 insertions(+), 47 deletions(-) diff --git a/server/src/test/java/io/spine/server/storage/EntityRecordStorageTest.java b/server/src/test/java/io/spine/server/storage/EntityRecordStorageTest.java index dbe910b062..922d7465e0 100644 --- a/server/src/test/java/io/spine/server/storage/EntityRecordStorageTest.java +++ b/server/src/test/java/io/spine/server/storage/EntityRecordStorageTest.java @@ -40,7 +40,6 @@ import io.spine.server.entity.EntityRecord; import io.spine.server.entity.TransactionalEntity; import io.spine.server.entity.storage.EntityRecordStorage; -import io.spine.server.entity.storage.EntityRecordWithColumns; import io.spine.server.storage.given.EntityRecordStorageTestEnv.TestCounterEntity; import io.spine.server.storage.given.GivenStorageProject; import io.spine.test.storage.StgProject; @@ -75,6 +74,7 @@ import static io.spine.server.storage.given.EntityRecordStorageTestEnv.newRecord; import static io.spine.server.storage.given.EntityRecordStorageTestEnv.recordWithCols; import static io.spine.server.storage.given.EntityRecordStorageTestEnv.recordsWithColumnsFrom; +import static io.spine.server.storage.given.GivenStorageProject.newEntityRecord; import static io.spine.server.storage.given.GivenStorageProject.newState; import static io.spine.test.storage.StgProject.Status.CANCELLED; import static io.spine.test.storage.StgProject.Status.CANCELLED_VALUE; @@ -491,13 +491,12 @@ void severalNewRecords() { var storage = storage(); var bulkSize = 5; - Map> initial = + Map> initial = new HashMap<>(bulkSize); for (var i = 0; i < bulkSize; i++) { - var id = newId(); - var record = newStorageRecord(id); - initial.put(id, newRecord(id, record)); + var record = newEntityRecord(); + initial.put(record.id(), record); } storage.writeAll(initial.values()); diff --git a/server/src/test/java/io/spine/server/storage/given/EntityRecordStorageTestEnv.java b/server/src/test/java/io/spine/server/storage/given/EntityRecordStorageTestEnv.java index 0834d9c622..256055898f 100644 --- a/server/src/test/java/io/spine/server/storage/given/EntityRecordStorageTestEnv.java +++ b/server/src/test/java/io/spine/server/storage/given/EntityRecordStorageTestEnv.java @@ -37,12 +37,10 @@ import io.spine.server.entity.Entity; import io.spine.server.entity.EntityRecord; import io.spine.server.entity.HasLifecycleColumns; -import io.spine.server.entity.LifecycleFlags; import io.spine.server.entity.StorageConverter; import io.spine.server.entity.TestTransaction; import io.spine.server.entity.TransactionalEntity; import io.spine.server.entity.storage.EntityRecordStorage; -import io.spine.server.entity.storage.EntityRecordWithColumns; import io.spine.server.entity.storage.SpecScanner; import io.spine.server.entity.storage.given.TaskViewProjection; import io.spine.server.storage.MessageRecordSpec; @@ -80,41 +78,6 @@ private EntityRecordStorageTestEnv() { private static final MessageRecordSpec spec = SpecScanner.scan(TestCounterEntity.class); - public static EntityRecord buildStorageRecord(StgProjectId id, - EntityState state) { - var wrappedState = pack(state); - var record = EntityRecord.newBuilder() - .setEntityId(pack(id)) - .setState(wrappedState) - .setVersion(GivenVersion.withNumber(0)) - .build(); - return record; - } - - public static EntityRecord buildStorageRecord(StgProjectId id, - EntityState state, - LifecycleFlags lifecycleFlags) { - var wrappedState = pack(state); - var record = EntityRecord.newBuilder() - .setEntityId(pack(id)) - .setState(wrappedState) - .setVersion(GivenVersion.withNumber(0)) - .setLifecycleFlags(lifecycleFlags) - .build(); - return record; - } - - public static EntityRecord buildStorageRecord(TestCounterEntity entity) { - var wrappedState = pack(entity.state()); - var record = EntityRecord.newBuilder() - .setEntityId(pack(entity.id())) - .setState(wrappedState) - .setVersion(GivenVersion.withNumber(0)) - .setLifecycleFlags(entity.lifecycleFlags()) - .build(); - return record; - } - /** * Creates new instance of the test entity. */ @@ -185,11 +148,6 @@ var record = StorageConverter.toEntityRecord(entity) return RecordWithColumns.create(record, SpecScanner.scan(entity)); } - public static EntityRecordWithColumns - newRecord(StgProjectId id, EntityRecord record) { - return EntityRecordWithColumns.create(id, record); - } - public static void assertQueryHasSingleResult( StgProject.Query query, EntityRecord expected, From b6b7338c8f76e6439319ba43b05596fba7297de3 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 2 Nov 2023 15:01:34 +0000 Subject: [PATCH 27/55] Update the documentation. --- .../io/spine/server/entity/storage/EntityRecordStorage.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/io/spine/server/entity/storage/EntityRecordStorage.java b/server/src/main/java/io/spine/server/entity/storage/EntityRecordStorage.java index 621b97483a..e33d5da4f8 100644 --- a/server/src/main/java/io/spine/server/entity/storage/EntityRecordStorage.java +++ b/server/src/main/java/io/spine/server/entity/storage/EntityRecordStorage.java @@ -60,8 +60,8 @@ *

Delegates all storage operations to the underlying * {@link io.spine.server.storage.RecordStorage RecordStorage}. For that, creates a new * {@code RecordStorage} instance through the provided {@code StorageFactory} and configures - * it with the {@linkplain EntityRecordSpec record specification} corresponding - * to the stored Entity. + * it with the {@linkplain MessageRecordSpec record specification} corresponding + * to the stored {@code EntityRecord}. * * @param * the type of the identifiers of stored entities From 2eea61129baccafe83fb26a43083b75cc253cbfd Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 2 Nov 2023 15:02:23 +0000 Subject: [PATCH 28/55] Remove the unused method. --- .../io/spine/server/entity/storage/EntityRecordStorage.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/server/src/main/java/io/spine/server/entity/storage/EntityRecordStorage.java b/server/src/main/java/io/spine/server/entity/storage/EntityRecordStorage.java index e33d5da4f8..d6380be3f8 100644 --- a/server/src/main/java/io/spine/server/entity/storage/EntityRecordStorage.java +++ b/server/src/main/java/io/spine/server/entity/storage/EntityRecordStorage.java @@ -101,11 +101,6 @@ public EntityRecordStorage(ContextSpec context, this.stateClass = stateClassOf(entityClass); } - // TODO:alex.tymchenko:2023-10-27: kill! -// private static > EntityRecordSpec -// spec(Class> entityClass) { -// return EntityRecordSpec.of(entityClass); -// } @SuppressWarnings("unchecked" /* Safety of casts is guaranteed by `I` and `S` boundaries. */) private static > MessageRecordSpec From 7413d84f23260c4d71b0f500b9963dab19bbad4c Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 2 Nov 2023 15:27:55 +0000 Subject: [PATCH 29/55] Refine the API of `SpecScanner`. Cover its API with tests. --- .../entity/storage/EntityRecordStorage.java | 14 +-- .../server/entity/storage/SpecScanner.java | 80 +++++++++++++++-- .../server/entity/storage/AssertColumns.java | 22 +++++ .../server/entity/storage/ScannerTest.java | 78 ----------------- .../entity/storage/SpecScannerTest.java | 87 +++++++++++++++++++ 5 files changed, 181 insertions(+), 100 deletions(-) delete mode 100644 server/src/test/java/io/spine/server/entity/storage/ScannerTest.java create mode 100644 server/src/test/java/io/spine/server/entity/storage/SpecScannerTest.java diff --git a/server/src/main/java/io/spine/server/entity/storage/EntityRecordStorage.java b/server/src/main/java/io/spine/server/entity/storage/EntityRecordStorage.java index d6380be3f8..08f23de9db 100644 --- a/server/src/main/java/io/spine/server/entity/storage/EntityRecordStorage.java +++ b/server/src/main/java/io/spine/server/entity/storage/EntityRecordStorage.java @@ -47,7 +47,6 @@ import java.util.Iterator; import static com.google.common.base.Preconditions.checkState; -import static io.spine.server.entity.model.EntityClass.asEntityClass; import static io.spine.server.entity.model.EntityClass.stateClassOf; import static io.spine.server.entity.storage.EntityRecordColumn.archived; import static io.spine.server.entity.storage.EntityRecordColumn.deleted; @@ -96,22 +95,11 @@ public class EntityRecordStorage> public EntityRecordStorage(ContextSpec context, StorageFactory factory, Class> entityClass) { - super(context, factory.createRecordStorage(context, messageSpec(entityClass))); + super(context, factory.createRecordStorage(context, SpecScanner.scan(entityClass))); this.findActiveRecordsQuery = findActiveRecords(); this.stateClass = stateClassOf(entityClass); } - - @SuppressWarnings("unchecked" /* Safety of casts is guaranteed by `I` and `S` boundaries. */) - private static > MessageRecordSpec - messageSpec(Class> entityClass) { - var cls = asEntityClass(entityClass); - var idClass = (Class) cls.idClass(); - var stateClass = (Class) cls.stateClass(); - var spec = SpecScanner.scan(idClass, stateClass); - return spec; - } - /** * Returns the type of the state for the entities, which records are stored. */ diff --git a/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java b/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java index f37c82ce94..082d356190 100644 --- a/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java +++ b/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java @@ -26,6 +26,7 @@ package io.spine.server.entity.storage; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.protobuf.Any; @@ -60,14 +61,11 @@ import static java.util.Objects.requireNonNull; /** - * Scans Proto definitions of stored {@code Message}s and determines - * the record specification. + * Scans Proto definitions of Entities and their states, and determines + * the {@linkplain MessageRecordSpec record specification} for storage. * - *

Scans and extracts the definitions of {@link Column}s to be stored - * for a particular {@code Entity}. - * - *

The resulting columns include both the entity state-based columns declared with - * {@link io.spine.option.OptionsProto#column (column)} Proto option and the columns + *

The resulting spec includes both the entity state-based columns declared with + * {@link io.spine.option.OptionsProto#column (column)} Proto option, and the columns * storing lifecycle and version attributes of an {@code Entity}. * * @implNote Client-side API includes generic definitions of lifecycle and version columns @@ -105,6 +103,26 @@ public final class SpecScanner { private SpecScanner() { } + /** + * Determines the specification of the passed entity + * by the types of its ID and state. + * + *

The resulting specification is composed in relation + * to storing Entity as {@code EntityRecord}, along with some columns. + * + * @param idClass + * type of Entity identifiers + * @param stateClass + * type of Entity state + * @param + * type of Entity identifiers, as bounding generic parameter + * @param + * type of Entity state, bounded with {@code I} + * as a type of its identifier + * @return a new record specification + */ + @Internal + @VisibleForTesting public static > MessageRecordSpec scan(Class idClass, Class stateClass) { Set> accumulator = new HashSet<>(); @@ -129,6 +147,22 @@ idClass, EntityRecord.class, idFromRecord(), ImmutableSet.copyOf(accumulator) return result; } + /** + * Determines the specification of the passed entity + * by the Entity instance. + * + *

The resulting specification is composed in relation + * to storing Entity as {@code EntityRecord}, along with some columns. + * + * @param entity + * entity to scan + * @param + * type of Entity identifiers, as bounding generic parameter + * @param + * type of Entity state, bounded with {@code I} + * as a type of its identifier + * @return a new record specification + */ @SuppressWarnings("unchecked" /* Casts are ensured by `Entity` declaration. */) public static > MessageRecordSpec scan(Entity entity) { @@ -137,6 +171,22 @@ MessageRecordSpec scan(Entity entity) { return scan(entityCls); } + /** + * Determines the specification of the passed entity + * by the passed Entity class. + * + *

The resulting specification is composed in relation + * to storing Entity as {@code EntityRecord}, along with some columns. + * + * @param cls + * entity class to scan + * @param + * type of Entity identifiers, as bounding generic parameter + * @param + * type of Entity state, bounded with {@code I} + * as a type of its identifier + * @return a new record specification + */ public static > MessageRecordSpec scan(Class> cls) { checkNotNull(cls); @@ -151,6 +201,17 @@ Class castObject(Column stateCol) { return (Class) stateCol.type(); } + /** + * Unpacks Entity states from {@code Any} instances, caching the unpacked results. + * + *

This routine is used as a scoped cache for on-the-fly unpacking Entity state + * from {@code EntityRecord}s, and then passing them on to other operations, + * such as determining the column values. + * + * @param + * type of entit + * @param + */ private static final class MemoizingUnpacker> { private final Class stateCls; @@ -174,10 +235,11 @@ private synchronized S process(Any value) { @SuppressWarnings("ReturnOfNull" /* By design. */) private static > - Getter getter(Column stateColumn, MemoizingUnpacker unpacker) { + Getter getter(Column stateColumn, + MemoizingUnpacker unpacker) { return r -> { var state = r.getState(); - if(state.equals(Any.getDefaultInstance())) { + if (state.equals(Any.getDefaultInstance())) { // This may happen for `Aggregate` state, // if its visibility does not allow querying. return null; diff --git a/server/src/test/java/io/spine/server/entity/storage/AssertColumns.java b/server/src/test/java/io/spine/server/entity/storage/AssertColumns.java index 358de912d3..a2e2fc9d36 100644 --- a/server/src/test/java/io/spine/server/entity/storage/AssertColumns.java +++ b/server/src/test/java/io/spine/server/entity/storage/AssertColumns.java @@ -26,10 +26,17 @@ package io.spine.server.entity.storage; +import com.google.common.collect.ImmutableSet; +import io.spine.client.ArchivedColumn; +import io.spine.client.DeletedColumn; +import io.spine.client.VersionColumn; import io.spine.query.Column; import io.spine.query.ColumnName; import io.spine.query.EntityColumn; +import io.spine.server.entity.EntityRecord; +import io.spine.server.storage.MessageRecordSpec; +import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.truth.Truth.assertThat; /** @@ -51,4 +58,19 @@ static void assertContains(Iterable> columns, String colu } assertThat(contains).isTrue(); } + + static void assertHasLifecycleColumns(MessageRecordSpec spec) { + var columns = spec.columns(); + var names = columns.stream() + .map(Column::name) + .collect(toImmutableSet()); + + assertThat(names).containsAtLeastElementsIn( + ImmutableSet.of( + ArchivedColumn.instance().name(), + DeletedColumn.instance().name(), + VersionColumn.instance().name() + ) + ); + } } diff --git a/server/src/test/java/io/spine/server/entity/storage/ScannerTest.java b/server/src/test/java/io/spine/server/entity/storage/ScannerTest.java deleted file mode 100644 index 7e5b9aebca..0000000000 --- a/server/src/test/java/io/spine/server/entity/storage/ScannerTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2022, 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.entity.storage; - -import com.google.common.collect.ImmutableSet; -import io.spine.client.ArchivedColumn; -import io.spine.client.DeletedColumn; -import io.spine.client.VersionColumn; -import io.spine.query.Column; -import io.spine.server.entity.storage.given.TaskListViewProjection; -import io.spine.server.entity.storage.given.TaskViewProjection; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import static com.google.common.collect.ImmutableSet.toImmutableSet; -import static com.google.common.truth.Truth.assertThat; -import static io.spine.server.entity.model.EntityClass.asEntityClass; -import static io.spine.server.entity.storage.AssertColumns.assertContains; - -@DisplayName("`Scanner` should") -class ScannerTest { - - // TODO:alex.tymchenko:2023-10-27: adapt to `SpecScanner`? -// @Test -// @DisplayName("include `Entity` lifecycle columns into the scan results") -// void extractSystemColumns() { -// var entityClass = asEntityClass(TaskViewProjection.class); -// var scanner = new Scanner<>(entityClass); -// var columns = scanner.columns(); -// -// var names = columns.stream() -// .map(Column::name) -// .collect(toImmutableSet()); -// -// assertThat(names).containsAtLeastElementsIn( -// ImmutableSet.of( -// ArchivedColumn.instance().name(), -// DeletedColumn.instance().name(), -// VersionColumn.instance().name() -// ) -// ); -// } -// -// @Test -// @DisplayName("extract the columns declared with `(column)` option in the Protobuf message") -// void extractSimpleColumns() { -// var entityClass = asEntityClass(TaskListViewProjection.class); -// var scanner = new Scanner<>(entityClass); -// -// var columns = scanner.stateColumns(); -// -// assertContains(columns, "description"); -// } -} diff --git a/server/src/test/java/io/spine/server/entity/storage/SpecScannerTest.java b/server/src/test/java/io/spine/server/entity/storage/SpecScannerTest.java new file mode 100644 index 0000000000..42016c3737 --- /dev/null +++ b/server/src/test/java/io/spine/server/entity/storage/SpecScannerTest.java @@ -0,0 +1,87 @@ +/* + * Copyright 2022, 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.entity.storage; + +import io.spine.server.entity.storage.given.TaskListViewProjection; +import io.spine.server.entity.storage.given.TaskViewProjection; +import io.spine.test.storage.StgProject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static io.spine.server.entity.storage.AssertColumns.assertContains; +import static io.spine.server.entity.storage.AssertColumns.assertHasLifecycleColumns; +import static io.spine.server.storage.given.EntityRecordStorageTestEnv.newEntity; +import static io.spine.server.storage.given.GivenStorageProject.newId; +import static io.spine.test.entity.TaskListView.Column.description; + +@DisplayName("`SpecScanner` should") +class SpecScannerTest { + + @Nested + @DisplayName("scan `Entity` state class") + class ScanEntityState { + @Test + @DisplayName("and include `Entity` lifecycle columns into the scan results") + void lifecycleCols() { + var spec = SpecScanner.scan(TaskViewProjection.class); + assertHasLifecycleColumns(spec); + } + + @Test + @DisplayName("extract the columns declared with `(column)` option in the Protobuf message") + void stateCols() { + var spec = SpecScanner.scan(TaskListViewProjection.class); + var columns = spec.columns(); + assertContains(columns, description().name().value()); + } + } + + @Nested + @DisplayName("scan `Entity` class") + class ScanEntityClass { + @Test + @DisplayName("and have `Entity` lifecycle columns among the scan results") + void lifecycleCols() { + var entity = newEntity(newId()); + var spec = SpecScanner.scan(entity); + assertHasLifecycleColumns(spec); + } + + @Test + @DisplayName("extract the columns marked with `(column)` option in the Protobuf message") + void stateCols() { + var entity = newEntity(newId()); + var spec = SpecScanner.scan(entity); + var columns = spec.columns(); + + for (var expected : StgProject.Column.definitions()) { + assertContains(columns, expected.name().value()); + } + } + } +} From bb2fef067b95057f271f357442770bb10824c6f7 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 2 Nov 2023 15:28:22 +0000 Subject: [PATCH 30/55] Delete old `Scanner` type. --- .../spine/server/entity/storage/Scanner.java | 158 ------------------ 1 file changed, 158 deletions(-) delete mode 100644 server/src/main/java/io/spine/server/entity/storage/Scanner.java diff --git a/server/src/main/java/io/spine/server/entity/storage/Scanner.java b/server/src/main/java/io/spine/server/entity/storage/Scanner.java deleted file mode 100644 index 3ddb89f3e0..0000000000 --- a/server/src/main/java/io/spine/server/entity/storage/Scanner.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright 2022, 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.entity.storage; - -import io.spine.client.ArchivedColumn; -import io.spine.query.Column; -import io.spine.query.EntityColumn; - -/** - * Scans and extracts the definitions of {@link Column}s to be stored - * for a particular {@code Entity}. - * - *

The resulting columns include both the entity state-based columns declared with - * {@link io.spine.option.OptionsProto#column (column)} Proto option and the columns - * storing lifecycle and version attributes of an {@code Entity}. - * - * @param - * the type of {@code Entity} to scan - * @param - * the type of the state for the scanned {@code Entity} - * @implNote Client-side API includes generic definitions of lifecycle and version columns - * (such as {@link ArchivedColumn}). However, their code cannot depend on the {@code Entity} - * type directly, as the {@code client} module has no dependency on {@code server} module. - * Therefore, this column scanning process wires those generic column definitions with an - * actual {@code Entity} type, instances of which serve as a data source for each column. - * Also, instead of scanning the {@code (column)} options from an entity state - * {@code Message} directly, this scanner uses a Spine compiler-generated shortcut method - * called {@code definitions()} which returns the set of {@link EntityColumn}s. - * Such an approach improves the scanning performance and preserve the types of generic - * parameters code-generated for each {@code EntityColumn}. - */ -final class Scanner/*, E extends Entity>*/ { - - // TODO:alex.tymchenko:2023-10-27: kill and use `SpecScanner` under this name. -// /** -// * The name of the nested class generated by the Spine compiler as a container of -// * the entity column definitions. -// */ -// @SuppressWarnings("DuplicateStringLiteralInspection") // coincidental duplication -// private static final String COLS_NESTED_CLASSNAME = "Column"; -// -// /** -// * The name of the method inside the column container class generated by the Spine compiler. -// * -// *

The method returns all the definitions of the columns for this state class. -// */ -// private static final String COL_DEFS_METHOD_NAME = "definitions"; -// -// /** -// * The target entity class to scan. -// */ -// private final EntityClass entityClass; -// -// /** -// * Creates an instance of scanner for a given type of {@code Entity}. -// */ -// Scanner(EntityClass entityClass) { -// this.entityClass = entityClass; -// } -// -// /** -// * Returns all columns for the scanned {@code Entity}. -// * -// *

The result includes both lifecycle columns and the columns declared -// * in the {@code Entity} state. -// */ -// EntityColumns columns() { -// Set> accumulator = new HashSet<>(); -// -// var stateColumns = stateColumns(); -// for (var stateCol : stateColumns) { -// Column wrapped = wrap(stateCol, (entity) -> stateCol.valueIn(entity.state())); -// accumulator.add(wrapped); -// } -// -// accumulator.add(wrap(ArchivedColumn.instance(), Entity::isArchived)); -// accumulator.add(wrap(DeletedColumn.instance(), Entity::isDeleted)); -// accumulator.add(wrap(VersionColumn.instance(), Entity::version)); -// -// var columns = new EntityColumns<>(accumulator); -// return columns; -// } -// -// private AsEntityColumn wrap(Column origin, Function getter) { -// return new AsEntityColumn<>(origin, getter); -// } -// -// /** -// * Obtains the {@linkplain EntityColumn entity-state-based} columns of the class. -// */ -// @SuppressWarnings("OverlyBroadCatchBlock") /* Treating all exceptions equally. */ -// StateColumns stateColumns() { -// var stateClass = entityClass.stateClass(); -// var columnClass = findColumnsClass(stateClass); -// if (columnClass == null) { -// return StateColumns.none(); -// } -// try { -// var getDefinitions = columnClass.getDeclaredMethod(COL_DEFS_METHOD_NAME); -// @SuppressWarnings("unchecked") // ensured by the Spine code generation. -// var columns = (Set>) getDefinitions.invoke(null); -// return new StateColumns<>(columns); -// } catch (Exception e) { -// throw newIllegalStateException( -// e, -// "Error fetching the declared columns by invoking the `%s.%s()` method" + -// " of the entity state type `%s`.", -// COLS_NESTED_CLASSNAME, COL_DEFS_METHOD_NAME, stateClass.getName()); -// } -// } -// -// /** -// * Finds the {@code Column} class which is generated the messages representing the entity -// * state type. -// * -// *

If an entity has no such class generated, it does not declare any columns. In this -// * case, this method returns {@code null}. -// * -// * @param stateClass -// * the class of the entity state to look for the method in -// * @return the class declaring the entity columns, -// * or {@code null} if the entity declares no columns -// */ -// private static @Nullable Class findColumnsClass(Class> stateClass) { -// var innerClasses = stateClass.getDeclaredClasses(); -// Class columnClass = null; -// for (var aClass : innerClasses) { -// if (COLS_NESTED_CLASSNAME.equals(aClass.getSimpleName())) { -// columnClass = aClass; -// } -// } -// return columnClass; -// } -} From 85fb91300fe139e4340e14277c107b35c75c40c5 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 2 Nov 2023 15:34:44 +0000 Subject: [PATCH 31/55] Update the docs of `MirrorMigration` to avoid mentioning `EntityRecordWithColumns`. --- .../migration/mirror/MirrorMigration.java | 17 ++++++++++------- .../migration/mirror/MirrorToEntityRecord.java | 7 +++---- .../mirror/MirrorToEntityRecordTest.java | 2 +- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/server/src/main/java/io/spine/server/migration/mirror/MirrorMigration.java b/server/src/main/java/io/spine/server/migration/mirror/MirrorMigration.java index a685cc7f41..f97e611e1e 100644 --- a/server/src/main/java/io/spine/server/migration/mirror/MirrorMigration.java +++ b/server/src/main/java/io/spine/server/migration/mirror/MirrorMigration.java @@ -33,7 +33,6 @@ import io.spine.server.aggregate.model.AggregateClass; import io.spine.server.entity.EntityRecord; import io.spine.server.entity.storage.EntityRecordStorage; -import io.spine.server.entity.storage.EntityRecordWithColumns; import io.spine.server.storage.RecordWithColumns; import io.spine.server.storage.StorageFactory; import io.spine.system.server.Mirror; @@ -44,7 +43,7 @@ import java.util.Iterator; /** - * Migrates {@link Mirror} projections into {@link EntityRecordWithColumns}. + * Migrates {@link Mirror} projections to {@code EntityRecord} with columns. * *

{@code Mirror} projection was deprecated in Spine 2.0. Previously, it was used to store * aggregates' states to allow their querying. A single projection stored states of all @@ -125,8 +124,8 @@ public MirrorMigration(ContextSpec context, this.entityRecordStorage = factory.createEntityRecordStorage(context, aggClass); this.transformation = new MirrorToEntityRecord<>(aggClass); this.aggregateType = AggregateClass.asAggregateClass(aggClass) - .stateTypeUrl() - .value(); + .stateTypeUrl() + .value(); } /** @@ -160,7 +159,8 @@ private MirrorsMigrated proceed(MirrorMigrationMonitor monitor) { mirrorStorage.writeBatch(batch.migratedMirrors()); var migrated = MirrorsMigrated.newBuilder() - .setValue(batch.migratedMirrors().size()) + .setValue(batch.migratedMirrors() + .size()) .build(); monitor.onBatchCompleted(migrated); @@ -169,8 +169,10 @@ private MirrorsMigrated proceed(MirrorMigrationMonitor monitor) { private Iterator fetchMirrors(int batchSize) { var query = mirrorStorage.queryBuilder() - .where(Mirror.Column.aggregateType()).is(aggregateType) - .where(Mirror.Column.wasMigrated()).is(false) + .where(Mirror.Column.aggregateType()) + .is(aggregateType) + .where(Mirror.Column.wasMigrated()) + .is(false) .sortAscendingBy(Mirror.Column.wasMigrated()) .limit(batchSize) .build(); @@ -196,6 +198,7 @@ EntityRecordStorage destinationStorage() { } private class MigrationBatch { + private final Collection> entityRecords; private final Collection migratedMirrors; diff --git a/server/src/main/java/io/spine/server/migration/mirror/MirrorToEntityRecord.java b/server/src/main/java/io/spine/server/migration/mirror/MirrorToEntityRecord.java index 709c93d66a..c0511e6986 100644 --- a/server/src/main/java/io/spine/server/migration/mirror/MirrorToEntityRecord.java +++ b/server/src/main/java/io/spine/server/migration/mirror/MirrorToEntityRecord.java @@ -29,7 +29,6 @@ import io.spine.base.EntityState; import io.spine.server.aggregate.Aggregate; import io.spine.server.entity.EntityRecord; -import io.spine.server.entity.storage.EntityRecordWithColumns; import io.spine.server.entity.storage.SpecScanner; import io.spine.server.storage.RecordWithColumns; import io.spine.system.server.Mirror; @@ -38,13 +37,13 @@ import java.util.function.Function; /** - * A transformation of a {@link Mirror} into an {@link EntityRecordWithColumns}. + * A transformation of a {@link Mirror} into an {@link RecordWithColumns}. * *

This transformation is applied to the mirror projections * in a scope of {@link MirrorMigration}. * *

{@code Mirror} itself can be directly transformed into an {@link EntityRecord}. - * In order to get {@code EntityRecordWithColumns}, we need to know which columns should be + * In order to get {@code RecordWithColumns}, we need to know which columns should be * fetched from the entity's state. For this reason, aggregate class is passed along * the mirror itself. It contains information about which columns are declared to be stored * along the aggregate's state. @@ -71,7 +70,7 @@ final class MirrorToEntityRecord, A extends Aggregat } /** - * Transforms the passed {@link Mirror} into an {@link EntityRecordWithColumns}. + * Transforms the passed {@link Mirror} into an {@link RecordWithColumns}. * *

The method will throw an exception when the mirror is incompatible * with the {@linkplain #MirrorToEntityRecord(Class) used aggregate class}. Meaning, diff --git a/server/src/test/java/io/spine/server/migration/mirror/MirrorToEntityRecordTest.java b/server/src/test/java/io/spine/server/migration/mirror/MirrorToEntityRecordTest.java index d64abf536d..c4b82abfaa 100644 --- a/server/src/test/java/io/spine/server/migration/mirror/MirrorToEntityRecordTest.java +++ b/server/src/test/java/io/spine/server/migration/mirror/MirrorToEntityRecordTest.java @@ -48,7 +48,7 @@ final class MirrorToEntityRecordTest { @Test - @DisplayName("transform a `Mirror` into an `EntityRecordWithColumns`") + @DisplayName("transform a `Mirror` into an `RecordWithColumns`") void mapMirrorToEntityRecord() { Function> transformation = new MirrorToEntityRecord<>(ParcelAgg.class); From c409af84d07072e8ed47b6f978b44c74e0accd91 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 2 Nov 2023 15:35:05 +0000 Subject: [PATCH 32/55] Get rid of `EntityRecordWithColumns`, as it's no longer used. --- .../server/entity/RecordBasedRepository.java | 1 - .../storage/EntityRecordWithColumns.java | 234 ------------------ .../storage/EntityRecordWithColumnsTest.java | 30 --- 3 files changed, 265 deletions(-) delete mode 100644 server/src/main/java/io/spine/server/entity/storage/EntityRecordWithColumns.java diff --git a/server/src/main/java/io/spine/server/entity/RecordBasedRepository.java b/server/src/main/java/io/spine/server/entity/RecordBasedRepository.java index c8dbc71756..d032644c47 100644 --- a/server/src/main/java/io/spine/server/entity/RecordBasedRepository.java +++ b/server/src/main/java/io/spine/server/entity/RecordBasedRepository.java @@ -42,7 +42,6 @@ import io.spine.core.Signal; import io.spine.query.EntityQuery; import io.spine.server.entity.storage.EntityRecordStorage; -import io.spine.server.entity.storage.EntityRecordWithColumns; import io.spine.server.entity.storage.ToEntityRecordQuery; import io.spine.server.storage.QueryConverter; import io.spine.server.storage.RecordWithColumns; diff --git a/server/src/main/java/io/spine/server/entity/storage/EntityRecordWithColumns.java b/server/src/main/java/io/spine/server/entity/storage/EntityRecordWithColumns.java deleted file mode 100644 index a2a7f7b367..0000000000 --- a/server/src/main/java/io/spine/server/entity/storage/EntityRecordWithColumns.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright 2022, 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.entity.storage; - -import com.google.common.annotations.VisibleForTesting; -import io.spine.annotation.SPI; -import io.spine.base.Identifier; -import io.spine.query.ColumnName; -import io.spine.server.entity.EntityRecord; -import io.spine.server.entity.LifecycleFlags; -import io.spine.server.entity.WithLifecycle; -import io.spine.server.storage.RecordWithColumns; -import org.checkerframework.checker.nullness.qual.Nullable; - -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -import static com.google.common.base.Preconditions.checkNotNull; -import static java.util.Collections.emptyMap; - -/** - * A value of {@link EntityRecord} associated with the values - * of its {@linkplain io.spine.query.Column columns}. - * - * @param - * the type of entity identifiers - */ -@SPI -public final class EntityRecordWithColumns - extends RecordWithColumns implements WithLifecycle { - - private EntityRecordWithColumns(I id, EntityRecord record, Map columns) { - super(id, record, columns); - } - - // TODO:alex.tymchenko:2023-10-27: kill! -// /** -// * Creates the new instance of {@code EntityRecordWithColumns} by evaluating the values -// * of the passed columns for the passed entity. -// * -// * @param record -// * the record prepared for storage -// * @param -// * the type of the entity identifiers -// * @return a new instance of {@code EntityRecordWithColumns} -// */ -// public static , E extends Entity> -// EntityRecordWithColumns create(E entity, EntityRecord record) { -// checkNotNull(entity); -// checkNotNull(record); -// var recordSpec = EntityRecordSpec.of(entity); -// var storageFields = recordSpec.valuesIn(entity); -// return new EntityRecordWithColumns<>(entity.id(), record, storageFields); -// } - - /** - * Creates the new instance of {@code EntityRecordWithColumns} using the pre-created - * entity record and the entity identifier. - * - *

This method considers only the values of the - * {@linkplain EntityRecordColumn lifecycle columns}. - * - * @param id - * the identifier of the entity - * @param record - * the record to store; it is also used as a source for the lifecycle column values - * @param - * the type of the identifiers - * @return a new instance of {@code EntityRecordWithColumns} - */ - public static EntityRecordWithColumns create(I id, EntityRecord record) { - checkNotNull(id); - checkNotNull(record); - var lifecycleValues = EntityRecordColumn.valuesIn(record); - return new EntityRecordWithColumns<>(id, record, lifecycleValues); - } - -// /** -// * Creates a new instance of {@code EntityRecordWithColumns} using the pre-created -// * entity record and the entity class. -// * -// *

This method considers both lifecycle and state-based columns. -// * -// *

It is a responsibility of the caller to provide a record with the matching -// * identifier and state types. -// * -// * @param record -// * the record prepared for storage -// * @param entityClass -// * the class of entity which is stored as the given {@code EntityRecord} -// * @param -// * the type of the entity's identifier -// * @param -// * the type of the entity's state -// * @param -// * the type of the entity -// */ -// @SuppressWarnings("unchecked") // see the docs. -// public static , E extends Entity> EntityRecordWithColumns -// create(EntityRecord record, Class entityClass) { -// checkNotNull(record); -// checkNotNull(entityClass); -// -// var stateValues = stateColumnsFrom(record, entityClass); -// var lifecycleValues = EntityRecordColumn.valuesIn(record); -// var allColumnValues = merge(stateValues, lifecycleValues); -// var result = (EntityRecordWithColumns) of(record, allColumnValues); -// return result; -// } - - // TODO:alex.tymchenko:2023-10-27: kill? -// @SuppressWarnings("unchecked") /* See the docs for `create(record, entityClass)`. */ -// private static , E extends Entity> -// Map stateColumnsFrom(EntityRecord record, Class entityClass) { -// // TODO:alex.tymchenko:2023-10-27: reuse this expression? -// var entityClazz = EntityClass.asParameterizedEntityClass(entityClass); -// var columnsScanner = new Scanner<>(entityClazz); -// var entityState = (S) AnyPacker.unpack(record.getState()); -// -// var stateValues = columnsScanner.stateColumns().valuesIn(entityState); -// return stateValues; -// } - - private static Map merge(Map stateValues, - Map lifecycleValues) { - Map allColumnValues = new HashMap<>(); - allColumnValues.putAll(stateValues); - allColumnValues.putAll(lifecycleValues); - return allColumnValues; - } - - /** - * Wraps a passed entity record into a {@code EntityWithColumns} with no storage fields. - * - *

This is a shortcut for {@link #of(EntityRecord, Map) of(EntityRecord, Map)} with - * an empty {@code Map} of storage fields. - * - * @see #of(EntityRecord, Map) for the notes on usage - */ - @VisibleForTesting - public static EntityRecordWithColumns of(EntityRecord record) { - return of(record, emptyMap()); - } - - /** - * Creates a new instance from the passed record and storage fields. - * - * @apiNote This test-only method unpacks the identifier of the passed record and casts - * it to the type {@code I}. It is a responsibility of the caller to provide the record - * with the matching identifier. - */ - @VisibleForTesting - public static EntityRecordWithColumns - of(EntityRecord record, Map storageFields) { - I id = extractId(record); - return new EntityRecordWithColumns<>(id, record, storageFields); - } - - /** - * Extracts the identifier from the passed record and casts it to the type {@code I}. - * - *

It is a responsibility of the caller to provide a record with the matching identifier. - */ - @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) // see the docs. - private static I extractId(EntityRecord record) { - return (I) Identifier.unpack(record.getEntityId()); - } - - @Override - public LifecycleFlags getLifecycleFlags() { - return record().getLifecycleFlags(); - } - - @Override - public boolean isArchived() { - return record().isArchived(); - } - - @Override - public boolean isDeleted() { - return record().isDeleted(); - } - - @Override - public boolean isActive() { - return record().isActive(); - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - var other = (EntityRecordWithColumns) o; - - return Objects.equals(id(), other.id()) && - Objects.equals(record(), other.record()) && - Objects.equals(storageFields(), other.storageFields()); - } - - @Override - public int hashCode() { - return Objects.hash(id(), record(), storageFields()); - } -} diff --git a/server/src/test/java/io/spine/server/entity/storage/EntityRecordWithColumnsTest.java b/server/src/test/java/io/spine/server/entity/storage/EntityRecordWithColumnsTest.java index d21e5d3e6e..d0a69fc51d 100644 --- a/server/src/test/java/io/spine/server/entity/storage/EntityRecordWithColumnsTest.java +++ b/server/src/test/java/io/spine/server/entity/storage/EntityRecordWithColumnsTest.java @@ -26,37 +26,7 @@ package io.spine.server.entity.storage; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import com.google.common.testing.EqualsTester; -import com.google.common.testing.NullPointerTester; -import com.google.protobuf.Any; -import io.spine.base.Identifier; -import io.spine.client.ArchivedColumn; -import io.spine.client.DeletedColumn; -import io.spine.query.ColumnName; -import io.spine.query.RecordColumn; -import io.spine.server.entity.EntityRecord; -import io.spine.server.entity.storage.given.TestEntity; -import io.spine.server.storage.RecordWithColumns; -import io.spine.server.storage.given.TestColumnMapping; -import io.spine.test.storage.StgProject; -import io.spine.test.storage.StgProjectId; -import io.spine.test.storage.StgTaskId; import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import java.util.HashMap; -import java.util.Map; - -import static com.google.common.collect.ImmutableSet.toImmutableSet; -import static com.google.common.truth.Truth.assertThat; -import static java.util.Collections.singletonMap; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; // TODO:alex.tymchenko:2023-10-28: migrate? @DisplayName("`EntityRecordWithColumns` should") From f7ad21ee72e6c3f890859d9eeab9127603f699da Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 2 Nov 2023 15:40:13 +0000 Subject: [PATCH 33/55] Update the documentation of `MessageRecordSpec` to avoid mentioning `EntityRecordSpec`. --- .../io/spine/server/storage/MessageRecordSpec.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/io/spine/server/storage/MessageRecordSpec.java b/server/src/main/java/io/spine/server/storage/MessageRecordSpec.java index 49c3dd15e9..0d4474014a 100644 --- a/server/src/main/java/io/spine/server/storage/MessageRecordSpec.java +++ b/server/src/main/java/io/spine/server/storage/MessageRecordSpec.java @@ -54,16 +54,17 @@ * the message record for further querying. Each column defines a way to calculate the stored value * basing on the passed message. * - *

This specification is not well-suited for describing the storage of - * {@link io.spine.server.entity.EntityRecord}s, as the values of their columns may be calculated - * from both the entity state and {@code Entity} instance attributes. - * See {@link io.spine.server.entity.storage.EntityRecordSpec EntityRecordSpec} for more details. + *

In case an instance of {@code EntityRecord} is described by this spec, + * a special set of accessors for ID and Entity state columns is set. + * This is done via {@link io.spine.server.entity.storage.SpecScanner SpecScanner} + * which perform analysis of Entity state, and creates getters for properties, + * considering that an instance of {@code Any} (being {@code EntityRecord.state} field) + * must be unpacked first. * * @param * the type of record identifiers * @param * the type of the record - * @see io.spine.server.entity.storage.EntityRecordSpec */ @Immutable public final class MessageRecordSpec extends RecordSpec { @@ -167,7 +168,7 @@ public Class sourceType() { } /** - * A method object to extract the value of an record identifier given an instance of a record. + * A method object to extract the value of a record identifier given an instance of a record. * *

Once some storage is passed a record to store, the value of the record identifier has * to be determined. To avoid passing the ID value for each record, one defines an way From 5008204365581968ce828ba2db1255f382775f30 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 2 Nov 2023 15:40:31 +0000 Subject: [PATCH 34/55] Kill unused methods. --- .../entity/storage/EntityRecordSpec.java | 51 ------------------- 1 file changed, 51 deletions(-) diff --git a/server/src/main/java/io/spine/server/entity/storage/EntityRecordSpec.java b/server/src/main/java/io/spine/server/entity/storage/EntityRecordSpec.java index c510aa0e54..746da516bb 100644 --- a/server/src/main/java/io/spine/server/entity/storage/EntityRecordSpec.java +++ b/server/src/main/java/io/spine/server/entity/storage/EntityRecordSpec.java @@ -83,57 +83,6 @@ private EntityRecordSpec(EntityClass entityClass, EntityColumns columns) { this.columns = columns; } - // TODO:alex.tymchenko:2023-10-27: kill? -// /** -// * Gathers columns declared by the entity class. -// * -// * @param entityClass -// * the class of the entity -// * @param -// * the type of the entity identifiers -// * @param -// * the type of the entity -// * @param -// * the type of the entity state -// */ -// public static , E extends Entity> -// EntityRecordSpec of(EntityClass entityClass) { -// checkNotNull(entityClass); -// var scanner = new Scanner<>(entityClass); -// return new EntityRecordSpec<>(entityClass, scanner.columns()); -// } - - // TODO:alex.tymchenko:2023-10-27: kill both! -// /** -// * Gathers columns declared by the class of the passed entity. -// * -// * @param entity -// * the entity instance -// * @param -// * the type of the entity identifiers -// * @param -// * the type of the entity -// * @param -// * the type of the entity state -// */ -// public static , E extends Entity> -// EntityRecordSpec of(E entity) { -// checkNotNull(entity); -// @SuppressWarnings("unchecked") // Ensured by the entity type declaration. -// var modelClass = (EntityClass) entity.modelClass(); -// var scanner = new Scanner<>(modelClass); -// return new EntityRecordSpec<>(modelClass, scanner.columns()); -// } -// -// /** -// * Gathers columns of the entity class. -// */ -// public static , E extends Entity> -// EntityRecordSpec of(Class cls) { -// var aClass = asParameterizedEntityClass(cls); -// return of(aClass); -// } - /** * Extracts column values from the entity. * From 1cfab2fd2d76f6045fe5fdaf72082c331c30dc03 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 3 Nov 2023 13:53:59 +0000 Subject: [PATCH 35/55] Migrate more tests to avoid using `EntityRecordWithColumns` and `EntityRecordSpec`. Continue to unify the conversion API related to `EntityRecord`. Remove old tests and duplicate code. --- .../entity/storage/EntityRecordColumn.java | 17 +- .../java/io/spine/server/stand/Stand.java | 12 +- .../entity/storage/EntityRecordSpecTest.java | 196 ---------------- .../storage/EntityRecordStorageTest.java | 4 +- .../server/storage/MessageRecordSpecTest.java | 222 ++++++++++++++---- .../storage/RecordStorageDelegateTest.java | 4 +- .../given/EntityRecordStorageTestEnv.java | 2 +- .../storage/given/GivenStorageProject.java | 12 +- .../spine/server/storage/given/StgColumn.java | 70 ------ .../storage/given/StgProjectStorage.java | 7 +- .../memory/RecordQueryMatcherTest.java | 4 +- 11 files changed, 212 insertions(+), 338 deletions(-) delete mode 100644 server/src/test/java/io/spine/server/entity/storage/EntityRecordSpecTest.java delete mode 100644 server/src/test/java/io/spine/server/storage/given/StgColumn.java diff --git a/server/src/main/java/io/spine/server/entity/storage/EntityRecordColumn.java b/server/src/main/java/io/spine/server/entity/storage/EntityRecordColumn.java index 578c639a46..e921af700c 100644 --- a/server/src/main/java/io/spine/server/entity/storage/EntityRecordColumn.java +++ b/server/src/main/java/io/spine/server/entity/storage/EntityRecordColumn.java @@ -29,6 +29,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import io.spine.annotation.Internal; import io.spine.client.ArchivedColumn; import io.spine.client.DeletedColumn; import io.spine.client.VersionColumn; @@ -59,12 +60,14 @@ * (i.e. {@link EntityRecordColumn#archived EntityRecordColumn.archived}). */ @RecordColumns(ofType = EntityRecord.class) -final class EntityRecordColumn { +@Internal +// TODO:alex.tymchenko:2023-11-03: review the access level and usages. +public final class EntityRecordColumn { /** * An {@link ArchivedColumn} which uses {@link EntityRecord} as a source for its values. */ - static final RecordColumn archived = + public static final RecordColumn archived = AsEntityRecordColumn.apply(ArchivedColumn.instance(), Boolean.class, EntityRecord::isArchived); @@ -72,7 +75,7 @@ final class EntityRecordColumn { /** * A {@link DeletedColumn} which uses {@link EntityRecord} as a source for its values. */ - static final RecordColumn deleted = + public static final RecordColumn deleted = AsEntityRecordColumn.apply(DeletedColumn.instance(), Boolean.class, EntityRecord::isDeleted); @@ -80,7 +83,7 @@ final class EntityRecordColumn { /** * A {@link VersionColumn} which uses {@link EntityRecord} as a source for its values. */ - static final RecordColumn version = + public static final RecordColumn version = AsEntityRecordColumn.apply(VersionColumn.instance(), Version.class, EntityRecord::getVersion); @@ -103,7 +106,7 @@ private EntityRecordColumn() { * Returns the names of all columns defined for storing of {@link EntityRecord}. */ @VisibleForTesting - static ImmutableSet names() { + public static ImmutableSet names() { return all.stream() .map(Column::name) .collect(toImmutableSet()); @@ -113,7 +116,7 @@ static ImmutableSet names() { * Evaluates the columns against the passed record and returns the value of each column * along with its name. */ - static ImmutableMap valuesIn(EntityRecord record) { + public static ImmutableMap valuesIn(EntityRecord record) { checkNotNull(record); ImmutableMap values = all.stream() @@ -125,7 +128,7 @@ static ImmutableMap valuesIn(EntityRecord record) { * Tells whether the passed column is either {@linkplain ArchivedColumn archived} * or {@linkplain DeletedColumn deleted}. */ - static boolean isLifecycleColumn(Column column) { + public static boolean isLifecycleColumn(Column column) { checkNotNull(column); var name = column.name(); return name.equals(archived.name()) || name.equals(deleted.name()); diff --git a/server/src/main/java/io/spine/server/stand/Stand.java b/server/src/main/java/io/spine/server/stand/Stand.java index f4763e382b..bc4b7f6179 100644 --- a/server/src/main/java/io/spine/server/stand/Stand.java +++ b/server/src/main/java/io/spine/server/stand/Stand.java @@ -30,7 +30,6 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.grpc.stub.StreamObserver; import io.spine.annotation.Internal; -import io.spine.base.Identifier; import io.spine.client.EntityStateWithVersion; import io.spine.client.Query; import io.spine.client.QueryResponse; @@ -40,15 +39,14 @@ import io.spine.core.Origin; import io.spine.core.Response; import io.spine.core.Responses; -import io.spine.protobuf.AnyPacker; import io.spine.server.EventProducer; import io.spine.server.Identity; import io.spine.server.bus.Listener; import io.spine.server.entity.Entity; import io.spine.server.entity.EntityLifecycle; -import io.spine.server.entity.EntityRecord; import io.spine.server.entity.EntityRecordChange; import io.spine.server.entity.Repository; +import io.spine.server.entity.StorageConverter; import io.spine.server.tenant.QueryOperation; import io.spine.server.tenant.SubscriptionOperation; import io.spine.server.tenant.TenantAwareOperation; @@ -146,12 +144,8 @@ public static Builder newBuilder() { */ @VisibleForTesting void post(Entity entity, EntityLifecycle lifecycle) { - var id = Identifier.pack(entity.id()); - var state = AnyPacker.pack(entity.state()); - var record = EntityRecord.newBuilder() - .setEntityId(id) - .setState(state) - .build(); + var record = StorageConverter.toEntityRecord(entity) + .build(); var change = EntityRecordChange.newBuilder() .setNewValue(record) .build(); diff --git a/server/src/test/java/io/spine/server/entity/storage/EntityRecordSpecTest.java b/server/src/test/java/io/spine/server/entity/storage/EntityRecordSpecTest.java deleted file mode 100644 index 75a6d37dab..0000000000 --- a/server/src/test/java/io/spine/server/entity/storage/EntityRecordSpecTest.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright 2022, 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.entity.storage; - -import com.google.common.collect.ImmutableSet; -import com.google.common.testing.NullPointerTester; -import io.spine.base.Identifier; -import io.spine.client.ArchivedColumn; -import io.spine.client.DeletedColumn; -import io.spine.client.VersionColumn; -import io.spine.core.Versions; -import io.spine.protobuf.AnyPacker; -import io.spine.query.Column; -import io.spine.query.ColumnName; -import io.spine.query.EntityColumn; -import io.spine.server.entity.EntityRecord; -import io.spine.server.entity.storage.given.TaskViewProjection; -import io.spine.test.entity.TaskView; -import io.spine.test.entity.TaskViewId; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import static com.google.common.collect.ImmutableSet.toImmutableSet; -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth8.assertThat; -import static io.spine.base.Time.currentTime; -import static io.spine.server.storage.given.EntityRecordStorageTestEnv.declaredColumns; -import static io.spine.server.storage.given.EntityRecordStorageTestEnv.spec; -import static io.spine.testing.DisplayNames.NOT_ACCEPT_NULLS; -import static org.junit.jupiter.api.Assertions.assertThrows; - -@SuppressWarnings("DuplicateStringLiteralInspection") -@DisplayName("`EntityRecordSpec` should") -class EntityRecordSpecTest { - - @Test - @DisplayName(NOT_ACCEPT_NULLS) - void passNullToleranceCheck() { - new NullPointerTester() - .testAllPublicStaticMethods(EntityRecordSpec.class); - new NullPointerTester() - .testAllPublicInstanceMethods(spec()); - } - - // TODO:alex.tymchenko:2023-10-27: rewrite! -// @Test -// @DisplayName("be extracted from an entity class") -// void beExtractedFromEntityClass() { -// EntityRecordSpec spec = EntityRecordSpec.of(TaskListViewProjection.class); -// var columnName = ColumnName.of("description"); -// var descriptionColumn = spec.findColumn(columnName); -// -// assertThat(descriptionColumn).isPresent(); -// } - - @Test - @DisplayName("obtain a column by name") - void obtainByName() { - declaredColumns().forEach( - EntityRecordSpecTest::testFindAndCompare - ); - } - - private static void testFindAndCompare(EntityColumn column) { - var columnName = column.name(); - var type = column.type(); - - assertThat(spec().get(columnName) - .type()).isEqualTo(type); - } - - @Test - @DisplayName("return all definitions of the columns") - void returnAllColumns() { - var columns = spec().columns(); - var actualNames = toNames(columns); - - var expected = ImmutableSet.>builder() - .addAll(declaredColumns()) - .add(ArchivedColumn.instance(), - DeletedColumn.instance(), - VersionColumn.instance()) - .build(); - var expectedNames = toNames(expected); - - assertThat(actualNames).containsExactlyElementsIn(expectedNames); - } - - private static ImmutableSet toNames(ImmutableSet> columns) { - return columns.stream() - .map(Column::name) - .collect(toImmutableSet()); - } - - @SuppressWarnings({"CheckReturnValue", "ResultOfMethodCallIgnored"}) - // Called to throw exception. - @Test - @DisplayName("throw `IAE` when the column with the specified name is not found") - void throwOnColumnNotFound() { - var nonExistent = ColumnName.of("non-existent-column"); - - assertThrows(IllegalArgumentException.class, () -> spec().get(nonExistent)); - } - - @Test - @DisplayName("search for a column by name") - void searchByName() { - var existingColumn = ColumnName.of("name"); - var column = spec().findColumn(existingColumn); - - assertThat(column).isPresent(); - } - - @Test - @DisplayName("return empty `Optional` when searching for a non-existent column") - void returnEmptyOptionalForNonExistent() { - var nonExistent = ColumnName.of("non-existent-column"); - var result = spec().findColumn(nonExistent); - - assertThat(result).isEmpty(); - } - - @Test - @DisplayName("return the list of columns") - void returnColumns() { - var lifecycleColumnCount = EntityRecordColumn.names() - .size(); - var protoColumnCount = 4; - - var expectedSize = lifecycleColumnCount + protoColumnCount; - assertThat(spec().columnCount()).isEqualTo(expectedSize); - } - - // TODO:alex.tymchenko:2023-10-27: rewrite or kill? -// @Test -// @DisplayName("extract values from entity") -// void extractColumnValues() { -// var projection = new TaskViewProjection(); -// var values = spec().valuesIn(projection); -// -// assertThat(values).containsExactly( -// ColumnName.of("archived"), projection.isArchived(), -// ColumnName.of("deleted"), projection.isDeleted(), -// ColumnName.of("version"), projection.version(), -// ColumnName.of("name"), projection.state() -// .getName(), -// ColumnName.of("estimate_in_days"), projection.state() -// .getEstimateInDays(), -// ColumnName.of("status"), projection.state() -// .getStatus(), -// ColumnName.of("due_date"), projection.state() -// .getDueDate() -// ); -// } - - @Test - @DisplayName("extract ID value from `EntityRecord`") - void extractIdValue() { - var projection = new TaskViewProjection(); - var state = projection.state(); - var id = TaskViewId.newBuilder() - .setId(93) - .build(); - var record = EntityRecord.newBuilder() - .setEntityId(Identifier.pack(id)) - .setState(AnyPacker.pack(state)) - .setVersion(Versions.newVersion(3, currentTime())) - .build(); - var actual = spec().idFromRecord(record); - assertThat(actual).isEqualTo(id); - } -} diff --git a/server/src/test/java/io/spine/server/storage/EntityRecordStorageTest.java b/server/src/test/java/io/spine/server/storage/EntityRecordStorageTest.java index 922d7465e0..e490c36270 100644 --- a/server/src/test/java/io/spine/server/storage/EntityRecordStorageTest.java +++ b/server/src/test/java/io/spine/server/storage/EntityRecordStorageTest.java @@ -74,7 +74,7 @@ import static io.spine.server.storage.given.EntityRecordStorageTestEnv.newRecord; import static io.spine.server.storage.given.EntityRecordStorageTestEnv.recordWithCols; import static io.spine.server.storage.given.EntityRecordStorageTestEnv.recordsWithColumnsFrom; -import static io.spine.server.storage.given.GivenStorageProject.newEntityRecord; +import static io.spine.server.storage.given.GivenStorageProject.newEntityRecordWithCols; import static io.spine.server.storage.given.GivenStorageProject.newState; import static io.spine.test.storage.StgProject.Status.CANCELLED; import static io.spine.test.storage.StgProject.Status.CANCELLED_VALUE; @@ -495,7 +495,7 @@ void severalNewRecords() { new HashMap<>(bulkSize); for (var i = 0; i < bulkSize; i++) { - var record = newEntityRecord(); + var record = newEntityRecordWithCols(); initial.put(record.id(), record); } storage.writeAll(initial.values()); diff --git a/server/src/test/java/io/spine/server/storage/MessageRecordSpecTest.java b/server/src/test/java/io/spine/server/storage/MessageRecordSpecTest.java index 6f2a4898e0..d2131fd6b0 100644 --- a/server/src/test/java/io/spine/server/storage/MessageRecordSpecTest.java +++ b/server/src/test/java/io/spine/server/storage/MessageRecordSpecTest.java @@ -26,17 +26,45 @@ package io.spine.server.storage; +import com.google.common.collect.ImmutableSet; import com.google.common.testing.NullPointerTester; +import io.spine.base.Identifier; +import io.spine.client.ArchivedColumn; +import io.spine.client.DeletedColumn; +import io.spine.client.VersionColumn; +import io.spine.core.Version; +import io.spine.protobuf.AnyPacker; +import io.spine.query.Column; +import io.spine.query.ColumnName; +import io.spine.query.EntityColumn; import io.spine.query.RecordColumn; -import io.spine.server.storage.given.StgColumn; +import io.spine.server.entity.EntityRecord; +import io.spine.server.entity.LifecycleFlags; +import io.spine.server.entity.storage.EntityRecordColumn; +import io.spine.server.storage.given.GivenStorageProject.StgProjectColumns; +import io.spine.test.entity.TaskView; import io.spine.test.storage.StgProject; import io.spine.test.storage.StgProjectId; +import io.spine.testdata.Sample; +import io.spine.testing.core.given.GivenVersion; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth8.assertThat; +import static io.spine.server.storage.given.EntityRecordStorageTestEnv.declaredColumns; +import static io.spine.server.storage.given.EntityRecordStorageTestEnv.taskViewSpec; +import static io.spine.server.storage.given.GivenStorageProject.messageSpec; +import static io.spine.server.storage.given.GivenStorageProject.newId; +import static io.spine.server.storage.given.GivenStorageProject.newState; +import static io.spine.test.entity.TaskView.Column.dueDate; +import static io.spine.test.entity.TaskView.Column.estimateInDays; +import static io.spine.test.entity.TaskView.Column.name; +import static io.spine.test.entity.TaskView.Column.status; import static io.spine.testing.DisplayNames.NOT_ACCEPT_NULLS; +import static org.junit.jupiter.api.Assertions.assertThrows; @DisplayName("`MessageRecordSpec` should") @SuppressWarnings("DuplicateStringLiteralInspection") /* Similar tests have similar names. */ @@ -46,55 +74,165 @@ class MessageRecordSpecTest { @DisplayName(NOT_ACCEPT_NULLS) void passNullToleranceCheck() { new NullPointerTester() - .testAllPublicInstanceMethods(spec()); + .testAllPublicInstanceMethods(messageSpec()); } - @Test - @DisplayName("obtain a column by name") - void obtainByName() { - var spec = spec(); - StgColumn.definitions() - .forEach(column -> assertColumn(spec, column)); - } + @Nested + @DisplayName("describe storage configuration of plain Proto message, and") + class PlainMessage { - @Test - @DisplayName("return all definitions of the columns") - void returnAllColumns() { - var actualColumns = spec().columns(); - assertThat(actualColumns).containsExactlyElementsIn(StgColumn.definitions()); - } + @Test + @DisplayName("obtain a column by name") + void obtainByName() { + var spec = messageSpec(); + StgProjectColumns.definitions() + .forEach(column -> assertColumn(spec, column)); + } - @Test - @DisplayName("return ID value by the record") - void returnIdValue() { - var id = projectId(); - var project = project(id); - var actual = spec().idFromRecord(project); - assertThat(actual).isEqualTo(id); - } + @SuppressWarnings("OptionalGetWithoutIsPresent" /* Checked via an assertion. */) + private void assertColumn(MessageRecordSpec spec, + RecordColumn column) { + var found = spec.findColumn(column.name()); + assertThat(found).isPresent(); + assertThat(found.get() + .type()).isEqualTo(column.type()); + } - private static StgProject project(StgProjectId id) { - return StgProject.newBuilder() - .setId(id) - .build(); - } + @Test + @DisplayName("return all definitions of the columns") + void returnAllColumns() { + var actualColumns = messageSpec().columns(); + assertThat(actualColumns).containsExactlyElementsIn(StgProjectColumns.definitions()); + } - private static StgProjectId projectId() { - return StgProjectId.newBuilder() - .setId("storage-project") - .build(); + @Test + @DisplayName("return ID value by the record") + void returnIdValue() { + var id = newId(); + var project = newState(id); + var actual = messageSpec().idFromRecord(project); + assertThat(actual).isEqualTo(id); + } } - private static MessageRecordSpec spec() { - return new MessageRecordSpec<>(StgProjectId.class, StgProject.class, - StgProject::getId, StgColumn.definitions()); - } + @Nested + @DisplayName("describe storage configuration of `EntityRecord` containing Entity state, and") + class WithEntityRecord { + + @Test + @DisplayName("return all definitions of the columns") + void returnAllColumns() { + var columns = taskViewSpec().columns(); + var actualNames = toNames(columns); + + var expected = ImmutableSet.>builder() + .addAll(declaredColumns()) + .add(ArchivedColumn.instance(), + DeletedColumn.instance(), + VersionColumn.instance()) + .build(); + var expectedNames = toNames(expected); + + assertThat(actualNames).containsExactlyElementsIn(expectedNames); + } + + private ImmutableSet toNames(ImmutableSet> columns) { + return columns.stream() + .map(Column::name) + .collect(toImmutableSet()); + } + + @Test + @DisplayName("return the number of columns") + void returnColumns() { + var lifecycleColumnCount = EntityRecordColumn.names() + .size(); + var protoColumnCount = 4; // See `task_view.proto`. + + var expectedSize = lifecycleColumnCount + protoColumnCount; + assertThat(taskViewSpec().columnCount()).isEqualTo(expectedSize); + } + + @Test + @DisplayName("obtain a column by name") + void obtainByName() { + declaredColumns().forEach(this::testGetAndCompare); + } + + private void testGetAndCompare(EntityColumn column) { + var columnName = column.name(); + var type = column.type(); + + assertThat(taskViewSpec().get(columnName) + .type()).isEqualTo(type); + } + + @Test + @DisplayName("search for a column by name") + void searchByName() { + var existingColumn = name(); + var column = taskViewSpec().findColumn(existingColumn.name()); + assertThat(column).isPresent(); + } + + @Test + @DisplayName("throw `IAE` when the column with the specified name is not found") + @SuppressWarnings("CheckReturnValue") + void throwOnColumnNotFound() { + var nonExistent = ColumnName.of("non-existent-column"); + + assertThrows(IllegalArgumentException.class, () -> taskViewSpec().get(nonExistent)); + } + + @Test + @DisplayName("return empty `Optional` when searching for a non-existent column") + void returnEmptyOptionalForNonExistent() { + var nonExistent = ColumnName.of("non-existent-column"); + var result = taskViewSpec().findColumn(nonExistent); + + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("extract values from `EntityRecord`") + void extractColumnValues() { + + var state = Sample.messageOfType(TaskView.class); + var archived = true; + var deleted = false; + var version = GivenVersion.withNumber(117); + var record = newEntityRecord(state, archived, deleted, version); + var spec = taskViewSpec(); + var values = spec.valuesIn(record); + + assertThat(values).containsExactly( + EntityRecordColumn.archived.name(), archived, + EntityRecordColumn.deleted.name(), deleted, + EntityRecordColumn.version.name(), version, + name().name(), state.getName(), + estimateInDays().name(), state.getEstimateInDays(), + status().name(), state.getStatus(), + dueDate().name(), state.getDueDate() + ); + var actual = spec.idFromRecord(record); + assertThat(actual) + .isEqualTo(state.getId()); + } - private static void assertColumn( - MessageRecordSpec spec, RecordColumn column) { - var found = spec.findColumn(column.name()); - assertThat(found).isPresent(); - assertThat(found.get() - .type()).isEqualTo(column.type()); + private EntityRecord newEntityRecord(TaskView state, + boolean archived, boolean deleted, + Version version) { + var flags = LifecycleFlags.newBuilder() + .setArchived(archived) + .setDeleted(deleted) + .build(); + var record = io.spine.server.entity.EntityRecord.newBuilder() + .setEntityId(Identifier.pack(state.getId())) + .setState(AnyPacker.pack(state)) + .setVersion(version) + .setLifecycleFlags(flags) + .build(); + return record; + } } } diff --git a/server/src/test/java/io/spine/server/storage/RecordStorageDelegateTest.java b/server/src/test/java/io/spine/server/storage/RecordStorageDelegateTest.java index a1d65ad34e..ecad7d43dd 100644 --- a/server/src/test/java/io/spine/server/storage/RecordStorageDelegateTest.java +++ b/server/src/test/java/io/spine/server/storage/RecordStorageDelegateTest.java @@ -54,14 +54,14 @@ import static com.google.protobuf.util.Timestamps.add; import static com.google.protobuf.util.Timestamps.subtract; import static io.spine.base.Time.currentTime; +import static io.spine.server.storage.given.GivenStorageProject.StgProjectColumns.due_date; +import static io.spine.server.storage.given.GivenStorageProject.StgProjectColumns.status; import static io.spine.server.storage.given.GivenStorageProject.newState; import static io.spine.server.storage.given.RecordStorageDelegateTestEnv.assertOnlyIdAndDueDate; import static io.spine.server.storage.given.RecordStorageDelegateTestEnv.coupleOfDone; import static io.spine.server.storage.given.RecordStorageDelegateTestEnv.dozenOfRecords; import static io.spine.server.storage.given.RecordStorageDelegateTestEnv.idAndDueDate; import static io.spine.server.storage.given.RecordStorageDelegateTestEnv.toIds; -import static io.spine.server.storage.given.StgColumn.due_date; -import static io.spine.server.storage.given.StgColumn.status; import static io.spine.test.storage.StgProject.Status.CREATED; import static io.spine.test.storage.StgProject.Status.DONE; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/server/src/test/java/io/spine/server/storage/given/EntityRecordStorageTestEnv.java b/server/src/test/java/io/spine/server/storage/given/EntityRecordStorageTestEnv.java index 256055898f..87ae5a4b27 100644 --- a/server/src/test/java/io/spine/server/storage/given/EntityRecordStorageTestEnv.java +++ b/server/src/test/java/io/spine/server/storage/given/EntityRecordStorageTestEnv.java @@ -160,7 +160,7 @@ public static void assertQueryHasSingleResult( return ImmutableSet.of(name(), estimateInDays(), status(), dueDate()); } - public static MessageRecordSpec spec() { + public static MessageRecordSpec taskViewSpec() { return SpecScanner.scan(TaskViewProjection.class); } diff --git a/server/src/test/java/io/spine/server/storage/given/GivenStorageProject.java b/server/src/test/java/io/spine/server/storage/given/GivenStorageProject.java index 3996df4097..5ef28296e6 100644 --- a/server/src/test/java/io/spine/server/storage/given/GivenStorageProject.java +++ b/server/src/test/java/io/spine/server/storage/given/GivenStorageProject.java @@ -47,6 +47,7 @@ import static io.spine.base.Identifier.newUuid; import static io.spine.base.Time.currentTime; +import static io.spine.query.RecordColumn.create; import static java.lang.String.format; import static java.lang.System.nanoTime; @@ -132,15 +133,15 @@ public static StgProject newState() { /** * Returns the record specification for {@code StgProject}. */ - public static MessageRecordSpec projectMessageSpec() { + public static MessageRecordSpec messageSpec() { return messageSpec; } /** - * Generates a new {@code StgProject} as Entity, + * Generates a new {@code StgProject} as an Entity state, * and wraps it into a {@code RecordWithColumns}. */ - public static RecordWithColumns newEntityRecord() { + public static RecordWithColumns newEntityRecordWithCols() { var project = newState(); var record = EntityRecord.newBuilder() .setEntityId(Identifier.pack(project.getId())) @@ -166,6 +167,9 @@ private StgProjectColumns() { public static final RecordColumn due_date = RecordColumn.create("due_date", Timestamp.class, StgProject::getDueDate); + public static final RecordColumn + status = create("status", String.class, (r) -> r.getStatus().name()); + public static final RecordColumn state_as_any = RecordColumn.create("state_as_any", Any.class, AnyPacker::pack); @@ -176,7 +180,7 @@ private StgProjectColumns() { * Returns all the column definitions. */ public static Columns definitions() { - return Columns.of(name, due_date, state_as_any); + return Columns.of(name, due_date, status, state_as_any); } } } diff --git a/server/src/test/java/io/spine/server/storage/given/StgColumn.java b/server/src/test/java/io/spine/server/storage/given/StgColumn.java deleted file mode 100644 index c7ae2e86ee..0000000000 --- a/server/src/test/java/io/spine/server/storage/given/StgColumn.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2022, 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.given; - -import com.google.protobuf.Timestamp; -import io.spine.query.Columns; -import io.spine.query.RecordColumn; -import io.spine.query.RecordColumns; -import io.spine.test.storage.StgProject; - -import static io.spine.query.RecordColumn.create; - -/** - * Columns of the {@link StgProject} stored as a plain {@link Message}. - */ -@RecordColumns(ofType = StgProject.class) -@SuppressWarnings( - {"DuplicateStringLiteralInspection", // Column names may repeat across records. - "BadImport"}) // `create` looks fine in this context. -public final class StgColumn { - - public static final RecordColumn - project_version = create("project_version", Integer.class, (r) -> r.getProjectVersion() - .getNumber()); - - public static final RecordColumn - due_date = create("due_date", Timestamp.class, StgProject::getDueDate); - - public static final RecordColumn - status = create("status", String.class, (r) -> r.getStatus() - .name()); - - /** - * Prevents this type from instantiation. - * - *

This class exists exclusively as a container of the column definitions. Thus it isn't - * expected to be instantiated at all. See the {@link RecordColumns} docs for more details on - * this approach. - */ - private StgColumn() { - } - - public static Columns definitions() { - return Columns.of(project_version, due_date, status); - } -} diff --git a/server/src/test/java/io/spine/server/storage/given/StgProjectStorage.java b/server/src/test/java/io/spine/server/storage/given/StgProjectStorage.java index 6e039a9281..c3793c8f17 100644 --- a/server/src/test/java/io/spine/server/storage/given/StgProjectStorage.java +++ b/server/src/test/java/io/spine/server/storage/given/StgProjectStorage.java @@ -30,6 +30,7 @@ import io.spine.server.storage.MessageRecordSpec; import io.spine.server.storage.RecordStorageUnderTest; import io.spine.server.storage.StorageFactory; +import io.spine.server.storage.given.GivenStorageProject.StgProjectColumns; import io.spine.test.storage.StgProject; import io.spine.test.storage.StgProjectId; @@ -39,10 +40,10 @@ *

While the message itself is marked as an entity state, this storage operates with it * like it is an ordinary message record. * - *

This storage defines several {@linkplain StgColumn custom columns} to use in tests. + *

This storage defines several {@linkplain StgProjectColumns custom columns} to use in tests. * * @see RecordStorageDelegateTest - * @see StgColumn + * @see StgProjectColumns */ public class StgProjectStorage extends RecordStorageUnderTest { @@ -53,7 +54,7 @@ public StgProjectStorage(ContextSpec context, StorageFactory factory) { private static MessageRecordSpec spec() { @SuppressWarnings("ConstantConditions") // Proto getters return non-{@code null} values. var spec = new MessageRecordSpec<>(StgProjectId.class, StgProject.class, StgProject::getId, - StgColumn.definitions()); + StgProjectColumns.definitions()); return spec; } } diff --git a/server/src/test/java/io/spine/server/storage/memory/RecordQueryMatcherTest.java b/server/src/test/java/io/spine/server/storage/memory/RecordQueryMatcherTest.java index 9920c238d9..17021b0b4e 100644 --- a/server/src/test/java/io/spine/server/storage/memory/RecordQueryMatcherTest.java +++ b/server/src/test/java/io/spine/server/storage/memory/RecordQueryMatcherTest.java @@ -37,7 +37,7 @@ import static com.google.common.truth.Truth.assertThat; import static io.spine.protobuf.AnyPacker.pack; import static io.spine.server.storage.given.GivenStorageProject.newState; -import static io.spine.server.storage.given.GivenStorageProject.projectMessageSpec; +import static io.spine.server.storage.given.GivenStorageProject.messageSpec; import static io.spine.server.storage.memory.given.RecordQueryMatcherTestEnv.newBuilder; import static io.spine.server.storage.memory.given.RecordQueryMatcherTestEnv.recordSubject; import static io.spine.testdata.Sample.messageOfType; @@ -95,7 +95,7 @@ void matchColumns() { @NonNull private static RecordWithColumns asRecord(StgProject state) { - return RecordWithColumns.create(state, projectMessageSpec()); + return RecordWithColumns.create(state, messageSpec()); } @Test From 891d2cf0705ad28c1d517c2c36d28076652a0506 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 3 Nov 2023 15:33:51 +0000 Subject: [PATCH 36/55] Revise API of `EntityRecordColumn`; use it in `SpecScanner`. --- .../entity/storage/EntityRecordColumn.java | 19 ++----------------- .../server/entity/storage/SpecScanner.java | 16 +++------------- 2 files changed, 5 insertions(+), 30 deletions(-) diff --git a/server/src/main/java/io/spine/server/entity/storage/EntityRecordColumn.java b/server/src/main/java/io/spine/server/entity/storage/EntityRecordColumn.java index e921af700c..82e40bd21d 100644 --- a/server/src/main/java/io/spine/server/entity/storage/EntityRecordColumn.java +++ b/server/src/main/java/io/spine/server/entity/storage/EntityRecordColumn.java @@ -27,7 +27,6 @@ package io.spine.server.entity.storage; import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import io.spine.annotation.Internal; import io.spine.client.ArchivedColumn; @@ -41,7 +40,6 @@ import io.spine.server.entity.EntityRecord; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.collect.ImmutableSet.toImmutableSet; /** @@ -59,9 +57,8 @@ * (e.g. {@link ArchivedColumn}) and its {@code EntityRecord}-based counterpart * (i.e. {@link EntityRecordColumn#archived EntityRecordColumn.archived}). */ -@RecordColumns(ofType = EntityRecord.class) @Internal -// TODO:alex.tymchenko:2023-11-03: review the access level and usages. +@RecordColumns(ofType = EntityRecord.class) public final class EntityRecordColumn { /** @@ -112,23 +109,11 @@ public static ImmutableSet names() { .collect(toImmutableSet()); } - /** - * Evaluates the columns against the passed record and returns the value of each column - * along with its name. - */ - public static ImmutableMap valuesIn(EntityRecord record) { - checkNotNull(record); - ImmutableMap values = - all.stream() - .collect(toImmutableMap(Column::name, c -> c.valueIn(record))); - return values; - } - /** * Tells whether the passed column is either {@linkplain ArchivedColumn archived} * or {@linkplain DeletedColumn deleted}. */ - public static boolean isLifecycleColumn(Column column) { + static boolean isLifecycleColumn(Column column) { checkNotNull(column); var name = column.name(); return name.equals(archived.name()) || name.equals(deleted.name()); diff --git a/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java b/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java index 082d356190..d58dad3e6d 100644 --- a/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java +++ b/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java @@ -34,15 +34,12 @@ import io.spine.base.EntityState; import io.spine.base.Identifier; import io.spine.client.ArchivedColumn; -import io.spine.client.DeletedColumn; -import io.spine.client.VersionColumn; import io.spine.query.Column; import io.spine.query.Column.Getter; import io.spine.query.EntityColumn; import io.spine.query.RecordColumn; import io.spine.server.entity.Entity; import io.spine.server.entity.EntityRecord; -import io.spine.server.entity.WithLifecycle; import io.spine.server.entity.model.EntityClass; import io.spine.server.storage.MessageRecordSpec; import io.spine.server.storage.MessageRecordSpec.ExtractId; @@ -53,7 +50,6 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import java.util.function.Function; import static com.google.common.base.Preconditions.checkNotNull; import static io.spine.protobuf.AnyPacker.unpack; @@ -137,9 +133,9 @@ private SpecScanner() { accumulator.add(recordColumn); } - accumulator.add(wrap(ArchivedColumn.instance(), WithLifecycle::isArchived)); - accumulator.add(wrap(DeletedColumn.instance(), WithLifecycle::isDeleted)); - accumulator.add(wrap(VersionColumn.instance(), EntityRecord::getVersion)); + accumulator.add(EntityRecordColumn.archived); + accumulator.add(EntityRecordColumn.deleted); + accumulator.add(EntityRecordColumn.version); var result = new MessageRecordSpec<>( idClass, EntityRecord.class, idFromRecord(), ImmutableSet.copyOf(accumulator) @@ -262,12 +258,6 @@ public I apply(@Nullable EntityRecord input) { }; } - private static RecordColumn - wrap(Column origin, Function getter) { - var result = new RecordColumn<>(origin.name(), origin.type(), getter::apply); - return result; - } - /** * Obtains the {@linkplain EntityColumn entity-state-based} columns of the class. */ From e90bc62b0235ac49d8ea6f88fb8e67092cc439fa Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 3 Nov 2023 15:38:36 +0000 Subject: [PATCH 37/55] Remove `EntityRecordSpec`, as it is no longer used. --- .../entity/storage/EntityRecordSpec.java | 173 -------------- .../storage/EntityRecordWithColumnsTest.java | 226 ------------------ 2 files changed, 399 deletions(-) delete mode 100644 server/src/main/java/io/spine/server/entity/storage/EntityRecordSpec.java delete mode 100644 server/src/test/java/io/spine/server/entity/storage/EntityRecordWithColumnsTest.java diff --git a/server/src/main/java/io/spine/server/entity/storage/EntityRecordSpec.java b/server/src/main/java/io/spine/server/entity/storage/EntityRecordSpec.java deleted file mode 100644 index 746da516bb..0000000000 --- a/server/src/main/java/io/spine/server/entity/storage/EntityRecordSpec.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright 2022, 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.entity.storage; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableSet; -import com.google.errorprone.annotations.Immutable; -import com.google.protobuf.Message; -import io.spine.annotation.Internal; -import io.spine.base.EntityState; -import io.spine.base.Identifier; -import io.spine.query.Column; -import io.spine.query.ColumnName; -import io.spine.server.entity.Entity; -import io.spine.server.entity.EntityRecord; -import io.spine.server.entity.model.EntityClass; -import io.spine.server.storage.RecordSpec; -import org.checkerframework.checker.nullness.qual.Nullable; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import static com.google.common.base.Preconditions.checkNotNull; -import static java.util.Collections.unmodifiableMap; - -/** - * Instructs the storage on how to handle {@link EntityRecord}s storing the information about - * {@link Entity} instances. - * - *

Lists the columns defined for the {@code Entity}, including the columns defined - * in the Protobuf message of the {@code Entity} state, and system columns. - * - *

In order to describe the specification of a plain Protobuf message stored, - * see {@link io.spine.server.storage.MessageRecordSpec MessageRecordSpec}. - * - * @param - * the type of the entity identifiers - * @param - * the type of entities - * @param - * the type of the entity states - */ -@Immutable -@Internal -public final class EntityRecordSpec, E extends Entity> - extends RecordSpec { - - /** - * The class of {@code Entity} which storage is configured. - */ - private final EntityClass entityClass; - - private final EntityColumns columns; - - private EntityRecordSpec(EntityClass entityClass, EntityColumns columns) { - super(idClass(entityClass), EntityRecord.class); - this.entityClass = entityClass; - this.columns = columns; - } - - /** - * Extracts column values from the entity. - * - *

The Protobuf-based columns are extracted from the entity state while the system columns - * are obtained from the entity itself via the corresponding getters. - * - * @apiNote This method returns an unmodifiable version of a {@code Map}. - * An {@link com.google.common.collect.ImmutableMap ImmutableMap} is not used, as long - * as it prohibits the {@code null} values. - */ - @Override - public Map valuesIn(E entity) { - checkNotNull(entity); - Map result = new HashMap<>(); - columns.forEach( - column -> result.put(column.name(), column.valueIn(entity)) - ); - return unmodifiableMap(result); - } - - @Override - public I idValueIn(E source) { - return source.id(); - } - - /** - * Extracts the entity identifier value from the passed {@code EntityRecord}. - * - *

Callers are responsible for passing the {@code EntityRecord} instances compatible - * with the contract of this record specification. In case the type of the identifier - * packed into the passed entity record is not {@code I}, - * a {@code ClassCastException} is thrown. - * - * @param record - * the source for extraction - * @return the value of the entity identifier - */ - @Override - @SuppressWarnings("unchecked") /* See the documentation. */ - public I idFromRecord(EntityRecord record) { - var packed = record.getEntityId(); - return (I) Identifier.unpack(packed); - } - - @Override - public Optional> findColumn(ColumnName name) { - checkNotNull(name); - for (Column column : columns) { - if(column.name().equals(name)) { - return Optional.of(column); - } - } - return Optional.empty(); - } - - @Override - public ImmutableSet> columns() { - var typed = columns.values(); - ImmutableSet.Builder> builder = ImmutableSet.builder(); - var result = builder.addAll(typed).build(); - return result; - } - - /** - * Returns the value of the entity class, which record spec this is. - */ - public EntityClass entityClass() { - return entityClass; - } - - @Override - public Class sourceType() { - return entityClass.stateClass(); - } - - /** - * Returns the total number of columns in this specification. - */ - @VisibleForTesting - int columnCount() { - return columns.size(); - } - - @SuppressWarnings("unchecked") // Ensured by the `Entity` declaration. - private static > Class idClass(EntityClass entityClass) { - return (Class) entityClass.idClass(); - } -} diff --git a/server/src/test/java/io/spine/server/entity/storage/EntityRecordWithColumnsTest.java b/server/src/test/java/io/spine/server/entity/storage/EntityRecordWithColumnsTest.java deleted file mode 100644 index d0a69fc51d..0000000000 --- a/server/src/test/java/io/spine/server/entity/storage/EntityRecordWithColumnsTest.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright 2022, 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.entity.storage; - -import org.junit.jupiter.api.DisplayName; - -// TODO:alex.tymchenko:2023-10-28: migrate? -@DisplayName("`EntityRecordWithColumns` should") -class EntityRecordWithColumnsTest { - -// private static final StgTaskId TASK_ID = -// StgTaskId.newBuilder() -// .setId(42) -// .build(); -// -// private static final StgProjectId PROJECT_ID = -// StgProjectId.newBuilder() -// .setId("42") -// .build(); -// -// private static final Any PACKED_TASK_ID = Identifier.pack(TASK_ID); -// -// private static EntityRecordWithColumns sampleRecordWithEmptyColumns() { -// return EntityRecordWithColumns.of(sampleEntityRecord()); -// } -// -// private static EntityRecordWithColumns randomRecordWithEmptyColumns() { -// return EntityRecordWithColumns.of(randomEntityRecord()); -// } -// -// private static EntityRecord sampleEntityRecord() { -// return EntityRecord.newBuilder() -// .setEntityId(PACKED_TASK_ID) -// .build(); -// } -// -// private static EntityRecord randomEntityRecord() { -// return EntityRecord.newBuilder() -// .setEntityId(Identifier.pack(Identifier.newUuid())) -// .build(); -// } -// -// @Test -// @DisplayName("not accept `null`s in constructor") -// void rejectNullInCtor() { -// new NullPointerTester() -// .setDefault(EntityRecord.class, sampleEntityRecord()) -// .testAllPublicConstructors(EntityRecordWithColumns.class); -// } -// -// @Test -// @DisplayName("support equality") -// void supportEquality() { -// var columnName = ArchivedColumn.instance().name(); -// Object value = false; -// EntityRecordWithColumns emptyFieldsEnvelope = -// EntityRecordWithColumns.of(sampleEntityRecord()); -// EntityRecordWithColumns notEmptyFieldsEnvelope = -// EntityRecordWithColumns.of(sampleEntityRecord(), singletonMap(columnName, value)); -// new EqualsTester() -// .addEqualityGroup(emptyFieldsEnvelope) -// .addEqualityGroup(notEmptyFieldsEnvelope) -// .addEqualityGroup(randomRecordWithEmptyColumns()) -// .addEqualityGroup( -// randomRecordWithEmptyColumns() // Each one has a different `EntityRecord`. -// ) -// .testEquals(); -// } -// -// @Test -// @DisplayName("return empty names collection if no storage fields are set") -// void returnEmptyColumns() { -// var record = sampleRecordWithEmptyColumns(); -// assertFalse(record.hasColumns()); -// var names = record.columnNames(); -// assertTrue(names.isEmpty()); -// } -// -// @Test -// @DisplayName("throw `ISE` on attempt to get value by non-existent name") -// void throwOnNonExistentColumn() { -// var record = sampleRecordWithEmptyColumns(); -// var nonExistentName = ColumnName.of("non-existent-column"); -// assertThrows(IllegalStateException.class, () -> record.columnValue(nonExistentName)); -// } -// -// // TODO:alex.tymchenko:2023-10-27: kill? -//// @Test -//// @DisplayName("have `Entity` lifecycle columns even if the entity does not define custom ones") -//// void supportEmptyColumns() { -//// var entity = new EntityWithoutCustomColumns(TASK_ID); -//// -//// var columns = EntityRecordSpec.of(entity); -//// var storageFields = columns.valuesIn(entity); -//// -//// var record = EntityRecordWithColumns.of(sampleEntityRecord(), storageFields); -//// assertThat(record.columnNames()) -//// .containsExactlyElementsIn(EntityRecordColumn.names()); -//// } -// -// @Test -// @DisplayName("return column value by column name") -// void returnColumnValue() { -// var columnName = ColumnName.of("some-boolean-column"); -// var columnValue = false; -// ImmutableMap storageFields = ImmutableMap.of(columnName, columnValue); -// var record = EntityRecordWithColumns.of(sampleEntityRecord(), storageFields); -// var value = record.columnValue(columnName); -// -// assertThat(value).isEqualTo(columnValue); -// } -// -// @Test -// @DisplayName("return a column value with the column mapping applied") -// void returnValueWithColumnMapping() { -// var columnName = ColumnName.of("some-int-column"); -// var columnValue = 42; -// -// ImmutableMap storageFields = ImmutableMap.of(columnName, columnValue); -// var record = EntityRecordWithColumns.of(sampleEntityRecord(), storageFields); -// var value = record.columnValue(columnName, new TestColumnMapping()); -// -// assertThat(value).isEqualTo(String.valueOf(columnValue)); -// } -// -// @Test -// @DisplayName("return `null` column value") -// void returnNullValue() { -// var columnName = ColumnName.of("the-null-column"); -// Map storageFields = new HashMap<>(); -// storageFields.put(columnName, null); -// var record = EntityRecordWithColumns.of(sampleEntityRecord(), storageFields); -// var value = record.columnValue(columnName); -// -// assertThat(value).isNull(); -// } -// -// @Nested -// @DisplayName("support being initialized with") -// class BeInitializedWith { -// -// // TODO:alex.tymchenko:2023-10-27: kill! -//// @Test -//// @DisplayName("an `Entity` identifier and a record") -//// void idAndRecord() { -//// var rawRecord = sampleEntityRecord(); -//// Long id = 199L; -//// var result = EntityRecordWithColumns.create(id, rawRecord); -//// assertThat(result).isNotNull(); -//// assertThat(result.id()).isEqualTo(id); -//// assertThat(result.record()).isEqualTo(rawRecord); -//// } -// -// @Test -// @DisplayName("an `Entity` and a record") -// void entityAndRecord() { -// var entity = new TestEntity(PROJECT_ID); -// var rawRecord = sampleEntityRecord(); -// var result = RecordWithColumns.create(rawRecord, SpecScanner.scan(entity)); -// assertThat(result).isNotNull(); -// assertThat(result.id()).isEqualTo(PROJECT_ID); -// assertThat(result.record()).isEqualTo(rawRecord); -// var names = result.columnNames(); -// var expectedNames = -// StgProject.Column.definitions() -// .stream() -// .map(RecordColumn::name) -// .collect(toImmutableSet()); -// assertThat(names).containsAtLeastElementsIn(expectedNames); -// assertThat(names).containsAtLeastElementsIn( -// ImmutableSet.of(ArchivedColumn.instance().name(), -// DeletedColumn.instance().name())); -// } -// } -// -// @Nested -// @DisplayName("store") -// class Store { -// -// @Test -// @DisplayName("record") -// void record() { -// var recordWithFields = sampleRecordWithEmptyColumns(); -// var record = recordWithFields.record(); -// assertNotNull(record); -// } -// -// @Test -// @DisplayName("column values") -// void columnValues() { -// var columnName = DeletedColumn.instance() -// .name(); -// Object value = false; -// var columnsExpected = singletonMap(columnName, value); -// var record = EntityRecordWithColumns.of(sampleEntityRecord(), columnsExpected); -// var columnNames = record.columnNames(); -// assertThat(columnNames).hasSize(1); -// assertTrue(columnNames.contains(columnName)); -// assertThat(value).isEqualTo(record.columnValue(columnName)); -// } -// } -} From b4722d2eff4cbbe9b2b21e70a69de01932ba0fcd Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 3 Nov 2023 16:01:02 +0000 Subject: [PATCH 38/55] Merge `MessageRecordSpec` with `RecordSpec` in favour of the latter. --- .../aggregate/AggregateEventStorage.java | 6 +- .../spine/server/delivery/CatchUpStorage.java | 12 +- .../spine/server/delivery/InboxStorage.java | 12 +- .../entity/storage/EntityRecordStorage.java | 6 +- .../server/entity/storage/SpecScanner.java | 14 +- .../server/event/store/DefaultEventStore.java | 8 +- .../migration/mirror/MirrorStorage.java | 10 +- .../server/storage/MessageRecordSpec.java | 196 ------------------ .../spine/server/storage/QueryConverter.java | 22 +- .../io/spine/server/storage/RecordSpec.java | 182 +++++++++++++--- .../spine/server/storage/RecordStorage.java | 6 +- .../server/storage/RecordStorageDelegate.java | 2 +- .../server/storage/RecordWithColumns.java | 11 +- .../spine/server/storage/StorageFactory.java | 2 +- .../storage/memory/InMemoryRecordStorage.java | 4 +- .../memory/InMemoryStorageFactory.java | 4 +- .../system/SystemAwareStorageFactory.java | 4 +- .../server/tenant/DefaultTenantStorage.java | 6 +- .../server/entity/storage/AssertColumns.java | 4 +- .../mirror/given/PreparedStorageFactory.java | 6 +- .../storage/ModelTestStorageFactory.java | 4 +- .../projection/ProjectionColumnTest.java | 4 +- .../server/storage/MessageRecordSpecTest.java | 6 +- .../given/EntityRecordStorageTestEnv.java | 6 +- .../storage/given/GivenStorageProject.java | 16 +- .../storage/given/StgProjectStorage.java | 6 +- .../system/SystemAwareStorageFactoryTest.java | 6 +- .../system/given/MemoizingStorageFactory.java | 4 +- 28 files changed, 251 insertions(+), 318 deletions(-) delete mode 100644 server/src/main/java/io/spine/server/storage/MessageRecordSpec.java diff --git a/server/src/main/java/io/spine/server/aggregate/AggregateEventStorage.java b/server/src/main/java/io/spine/server/aggregate/AggregateEventStorage.java index 673dd6358c..89b4b401c8 100644 --- a/server/src/main/java/io/spine/server/aggregate/AggregateEventStorage.java +++ b/server/src/main/java/io/spine/server/aggregate/AggregateEventStorage.java @@ -28,7 +28,7 @@ import io.spine.query.RecordQuery; import io.spine.server.ContextSpec; -import io.spine.server.storage.MessageRecordSpec; +import io.spine.server.storage.RecordSpec; import io.spine.server.storage.MessageStorage; import io.spine.server.storage.StorageFactory; @@ -46,8 +46,8 @@ public class AggregateEventStorage * A specification on how to store the event records of an aggregate. */ @SuppressWarnings("ConstantConditions") // Protobuf getters return non-{@code null} values. - private static final MessageRecordSpec spec = - new MessageRecordSpec<>( + private static final RecordSpec spec = + new RecordSpec<>( AggregateEventRecordId.class, AggregateEventRecord.class, AggregateEventRecord::getId, diff --git a/server/src/main/java/io/spine/server/delivery/CatchUpStorage.java b/server/src/main/java/io/spine/server/delivery/CatchUpStorage.java index 294c11b7cf..0abd6a71ec 100644 --- a/server/src/main/java/io/spine/server/delivery/CatchUpStorage.java +++ b/server/src/main/java/io/spine/server/delivery/CatchUpStorage.java @@ -29,7 +29,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import io.spine.annotation.SPI; -import io.spine.server.storage.MessageRecordSpec; +import io.spine.server.storage.RecordSpec; import io.spine.server.storage.MessageStorage; import io.spine.server.storage.StorageFactory; import io.spine.type.TypeUrl; @@ -63,11 +63,11 @@ public CatchUpStorage(StorageFactory factory, boolean multitenant) { } @SuppressWarnings("ConstantConditions") // Protobuf getters do not return {@code null}. - private static MessageRecordSpec getSpec() { - return new MessageRecordSpec<>(CatchUpId.class, - CatchUp.class, - CatchUp::getId, - CatchUpColumn.definitions()); + private static RecordSpec getSpec() { + return new RecordSpec<>(CatchUpId.class, + CatchUp.class, + CatchUp::getId, + CatchUpColumn.definitions()); } /** diff --git a/server/src/main/java/io/spine/server/delivery/InboxStorage.java b/server/src/main/java/io/spine/server/delivery/InboxStorage.java index 960f0a8c17..d97c016122 100644 --- a/server/src/main/java/io/spine/server/delivery/InboxStorage.java +++ b/server/src/main/java/io/spine/server/delivery/InboxStorage.java @@ -30,7 +30,7 @@ import com.google.protobuf.Timestamp; import io.spine.annotation.SPI; import io.spine.query.RecordQueryBuilder; -import io.spine.server.storage.MessageRecordSpec; +import io.spine.server.storage.RecordSpec; import io.spine.server.storage.MessageStorage; import io.spine.server.storage.StorageFactory; import org.checkerframework.checker.nullness.qual.Nullable; @@ -76,12 +76,12 @@ public InboxStorage(StorageFactory factory, boolean multitenant) { factory.createRecordStorage(Delivery.contextSpec(multitenant), spec())); } - private static MessageRecordSpec spec() { + private static RecordSpec spec() { @SuppressWarnings("ConstantConditions") // Protobuf getters do not return {@code null}s. - var spec = new MessageRecordSpec<>(InboxMessageId.class, - InboxMessage.class, - InboxMessage::getId, - InboxColumn.definitions()); + var spec = new RecordSpec<>(InboxMessageId.class, + InboxMessage.class, + InboxMessage::getId, + InboxColumn.definitions()); return spec; } diff --git a/server/src/main/java/io/spine/server/entity/storage/EntityRecordStorage.java b/server/src/main/java/io/spine/server/entity/storage/EntityRecordStorage.java index 08f23de9db..397b0f6776 100644 --- a/server/src/main/java/io/spine/server/entity/storage/EntityRecordStorage.java +++ b/server/src/main/java/io/spine/server/entity/storage/EntityRecordStorage.java @@ -39,7 +39,7 @@ import io.spine.server.ContextSpec; import io.spine.server.entity.Entity; import io.spine.server.entity.EntityRecord; -import io.spine.server.storage.MessageRecordSpec; +import io.spine.server.storage.RecordSpec; import io.spine.server.storage.RecordStorageDelegate; import io.spine.server.storage.RecordWithColumns; import io.spine.server.storage.StorageFactory; @@ -59,7 +59,7 @@ *

Delegates all storage operations to the underlying * {@link io.spine.server.storage.RecordStorage RecordStorage}. For that, creates a new * {@code RecordStorage} instance through the provided {@code StorageFactory} and configures - * it with the {@linkplain MessageRecordSpec record specification} corresponding + * it with the {@linkplain RecordSpec record specification} corresponding * to the stored {@code EntityRecord}. * * @param @@ -252,7 +252,7 @@ public boolean delete(I id) { */ @Internal @Override - public final MessageRecordSpec recordSpec() { + public final RecordSpec recordSpec() { return super.recordSpec(); } diff --git a/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java b/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java index d58dad3e6d..07e1f7b2cb 100644 --- a/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java +++ b/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java @@ -41,8 +41,8 @@ import io.spine.server.entity.Entity; import io.spine.server.entity.EntityRecord; import io.spine.server.entity.model.EntityClass; -import io.spine.server.storage.MessageRecordSpec; -import io.spine.server.storage.MessageRecordSpec.ExtractId; +import io.spine.server.storage.RecordSpec; +import io.spine.server.storage.RecordSpec.ExtractId; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -58,7 +58,7 @@ /** * Scans Proto definitions of Entities and their states, and determines - * the {@linkplain MessageRecordSpec record specification} for storage. + * the {@linkplain RecordSpec record specification} for storage. * *

The resulting spec includes both the entity state-based columns declared with * {@link io.spine.option.OptionsProto#column (column)} Proto option, and the columns @@ -119,7 +119,7 @@ private SpecScanner() { */ @Internal @VisibleForTesting - public static > MessageRecordSpec + public static > RecordSpec scan(Class idClass, Class stateClass) { Set> accumulator = new HashSet<>(); @@ -137,7 +137,7 @@ private SpecScanner() { accumulator.add(EntityRecordColumn.deleted); accumulator.add(EntityRecordColumn.version); - var result = new MessageRecordSpec<>( + var result = new RecordSpec<>( idClass, EntityRecord.class, idFromRecord(), ImmutableSet.copyOf(accumulator) ); return result; @@ -161,7 +161,7 @@ idClass, EntityRecord.class, idFromRecord(), ImmutableSet.copyOf(accumulator) */ @SuppressWarnings("unchecked" /* Casts are ensured by `Entity` declaration. */) public static > - MessageRecordSpec scan(Entity entity) { + RecordSpec scan(Entity entity) { checkNotNull(entity); var entityCls = (Class>) entity.getClass(); return scan(entityCls); @@ -184,7 +184,7 @@ MessageRecordSpec scan(Entity entity) { * @return a new record specification */ public static > - MessageRecordSpec scan(Class> cls) { + RecordSpec scan(Class> cls) { checkNotNull(cls); var idClass = EntityClass.idClass(cls); var stateClass = EntityClass.stateClassOf(cls); diff --git a/server/src/main/java/io/spine/server/event/store/DefaultEventStore.java b/server/src/main/java/io/spine/server/event/store/DefaultEventStore.java index 4bb5d1ab85..57e7227d9e 100644 --- a/server/src/main/java/io/spine/server/event/store/DefaultEventStore.java +++ b/server/src/main/java/io/spine/server/event/store/DefaultEventStore.java @@ -35,7 +35,7 @@ import io.spine.server.ContextSpec; import io.spine.server.event.EventStore; import io.spine.server.event.EventStreamQuery; -import io.spine.server.storage.MessageRecordSpec; +import io.spine.server.storage.RecordSpec; import io.spine.server.storage.MessageStorage; import io.spine.server.storage.RecordWithColumns; import io.spine.server.storage.StorageFactory; @@ -75,9 +75,9 @@ public DefaultEventStore(ContextSpec context, StorageFactory factory) { this.log = new Log(); } - private static MessageRecordSpec spec() { - var spec = new MessageRecordSpec<>(EventId.class, Event.class, Signal::id, - EventColumn.definitions()); + private static RecordSpec spec() { + var spec = new RecordSpec<>(EventId.class, Event.class, Signal::id, + EventColumn.definitions()); return spec; } diff --git a/server/src/main/java/io/spine/server/migration/mirror/MirrorStorage.java b/server/src/main/java/io/spine/server/migration/mirror/MirrorStorage.java index 93b9f7306d..d82adb1302 100644 --- a/server/src/main/java/io/spine/server/migration/mirror/MirrorStorage.java +++ b/server/src/main/java/io/spine/server/migration/mirror/MirrorStorage.java @@ -26,16 +26,16 @@ package io.spine.server.migration.mirror; +import com.google.common.collect.ImmutableList; import io.spine.query.RecordQuery; import io.spine.server.ContextSpec; -import io.spine.server.storage.MessageRecordSpec; import io.spine.server.storage.MessageStorage; +import io.spine.server.storage.RecordSpec; import io.spine.server.storage.StorageFactory; import io.spine.system.server.Mirror; import io.spine.system.server.MirrorId; import java.util.Iterator; -import java.util.List; /** * A contract for storages of legacy {@link Mirror} projections. @@ -63,12 +63,12 @@ public MirrorStorage(ContextSpec context, StorageFactory factory) { } @SuppressWarnings("ConstantConditions") - private static MessageRecordSpec messageSpec() { - var spec = new MessageRecordSpec<>( + private static RecordSpec messageSpec() { + var spec = new RecordSpec<>( MirrorId.class, Mirror.class, Mirror::getId, - List.of( + ImmutableList.of( Mirror.Column.aggregateType(), Mirror.Column.wasMigrated() ) diff --git a/server/src/main/java/io/spine/server/storage/MessageRecordSpec.java b/server/src/main/java/io/spine/server/storage/MessageRecordSpec.java deleted file mode 100644 index 0d4474014a..0000000000 --- a/server/src/main/java/io/spine/server/storage/MessageRecordSpec.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright 2022, 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; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import com.google.errorprone.annotations.Immutable; -import com.google.protobuf.Message; -import io.spine.query.Column; -import io.spine.query.ColumnName; -import io.spine.query.RecordColumn; -import org.checkerframework.checker.nullness.qual.Nullable; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.ImmutableMap.toImmutableMap; -import static com.google.common.collect.Streams.stream; - -/** - * Instructs storage implementations on how to store a plain Protobuf message as a storage record. - * - *

Defines the identifier column and the collection of the data columns to store along with - * the message record for further querying. Each column defines a way to calculate the stored value - * basing on the passed message. - * - *

In case an instance of {@code EntityRecord} is described by this spec, - * a special set of accessors for ID and Entity state columns is set. - * This is done via {@link io.spine.server.entity.storage.SpecScanner SpecScanner} - * which perform analysis of Entity state, and creates getters for properties, - * considering that an instance of {@code Any} (being {@code EntityRecord.state} field) - * must be unpacked first. - * - * @param - * the type of record identifiers - * @param - * the type of the record - */ -@Immutable -public final class MessageRecordSpec extends RecordSpec { - - /** - * A method object to extract the record identifier, once such a record is passed. - */ - private final ExtractId extractId; - - /** - * The columns to store along with the record itself. - */ - private final ImmutableMap> columns; - - /** - * Creates a new record specification listing the columns to store along with the record. - * - * @param idType - * the type of the record identifier - * @param recordType - * the type of the record - * @param extractId - * a method object to extract the value of an identifier given an instance of a record - * @param columns - * the definitions of the columns to store along with the record - */ - public MessageRecordSpec(Class idType, - Class recordType, - ExtractId extractId, - Iterable> columns) { - super(idType, recordType); - this.columns = - stream(columns).collect( - toImmutableMap(RecordColumn::name, (c) -> c) - ); - this.extractId = extractId; - } - - /** - * Creates a new record specification. - * - *

The specifications created implies that no columns are stored for the record. - * To define the stored columns, - * please use {@linkplain #MessageRecordSpec(Class, Class, ExtractId, Iterable) another ctor}. - * - * @param idType - * the type of the record identifier - * @param recordType - * the type of the record - * @param extractId - * a method object to extract the value of an identifier given an instance of a record - */ - public MessageRecordSpec(Class idType, Class recordType, ExtractId extractId) { - this(idType, recordType, extractId, ImmutableList.of()); - } - - @Override - public Map valuesIn(R record) { - checkNotNull(record); - Map result = new HashMap<>(); - columns.forEach( - (name, column) -> result.put(name, column.valueIn(record)) - ); - return result; - } - - @Override - public I idValueIn(R source) { - checkNotNull(source); - return extractId.apply(source); - } - - @Override - public I idFromRecord(R record) { - return idValueIn(record); - } - - @Override - public Optional> findColumn(ColumnName name) { - checkNotNull(name); - var result = columns.get(name); - return Optional.ofNullable(result); - } - - @Override - public ImmutableSet> columns() { - return ImmutableSet.copyOf(columns.values()); - } - - /** - * Returns the total number of columns in this specification. - */ - @VisibleForTesting - public int columnCount() { - return columns.size(); - } - - @Override - public Class sourceType() { - return storedType(); - } - - /** - * A method object to extract the value of a record identifier given an instance of a record. - * - *

Once some storage is passed a record to store, the value of the record identifier has - * to be determined. To avoid passing the ID value for each record, one defines an way - * to obtain the identifier value from the record instance itself — by defining - * an {@code ExtractId} as a part of the record specification for the storage. - * - * @param - * the type of records from which to extract the ID value - * @param - * the type of the record identifiers to retrieve - */ - @Immutable - @FunctionalInterface - public interface ExtractId extends Function { - - /** - * {@inheritDoc} - * - * This method differs from its parent by the fact it never returns {@code null}. - */ - @Override - @CanIgnoreReturnValue - I apply(@Nullable R input); - } -} diff --git a/server/src/main/java/io/spine/server/storage/QueryConverter.java b/server/src/main/java/io/spine/server/storage/QueryConverter.java index d4ddae1529..de249ef7f4 100644 --- a/server/src/main/java/io/spine/server/storage/QueryConverter.java +++ b/server/src/main/java/io/spine/server/storage/QueryConverter.java @@ -81,7 +81,7 @@ private QueryConverter() { * @return a new record query with the same semantic as the original filters and response format */ public static RecordQuery - convert(TargetFilters filters, ResponseFormat format, RecordSpec spec) { + convert(TargetFilters filters, ResponseFormat format, RecordSpec spec) { checkNotNull(spec); checkNotNull(filters); checkNotNull(format); @@ -115,7 +115,7 @@ private QueryConverter() { * @return a new record query */ public static RecordQuery - newQuery(RecordSpec spec, ResponseFormat format) { + newQuery(RecordSpec spec, ResponseFormat format) { checkNotNull(spec); checkNotNull(format); @@ -128,7 +128,7 @@ private QueryConverter() { } private static void - filters(RecordQueryBuilder builder, RecordSpec spec, TargetFilters filters) { + filters(RecordQueryBuilder builder, RecordSpec spec, TargetFilters filters) { for (var composite : filters.getFilterList()) { convertComposite(composite, builder, spec); } @@ -137,7 +137,7 @@ private QueryConverter() { private static void convertComposite(CompositeFilter composite, RecordQueryBuilder builder, - RecordSpec spec) { + RecordSpec spec) { var compositeOperator = composite.getOperator(); switch (compositeOperator) { @@ -168,7 +168,7 @@ private QueryConverter() { private static RecordQueryBuilder doCompositeWithAnd(CompositeFilter filter, RecordQueryBuilder builder, - RecordSpec spec) { + RecordSpec spec) { for (var childFilter : filter.getFilterList()) { doSimpleFilter(childFilter, builder, spec); } @@ -191,7 +191,7 @@ private QueryConverter() { private static RecordQueryBuilder doCompositeWithOr(CompositeFilter filter, RecordQueryBuilder builder, - RecordSpec spec) { + RecordSpec spec) { ImmutableList.Builder>> eithers = ImmutableList.builder(); @@ -214,7 +214,7 @@ private QueryConverter() { */ @SuppressWarnings("Immutable") /* Using `builder` in lambda is fine. */ private static ImmutableList>> - simpleFiltersToEither(List simpleFilters, RecordSpec spec) { + simpleFiltersToEither(List simpleFilters, RecordSpec spec) { return simpleFilters .stream() .map((filter) -> @@ -233,7 +233,7 @@ private QueryConverter() { */ @SuppressWarnings("Immutable") /* Using `builder` in lambda is fine. */ private static ImmutableList>> - compositesToEither(List composites, RecordSpec spec) { + compositesToEither(List composites, RecordSpec spec) { return composites .stream() .map((composite) -> @@ -250,7 +250,7 @@ private QueryConverter() { * according to the record specification. */ private static void - doSimpleFilter(Filter filter, RecordQueryBuilder builder, RecordSpec spec) { + doSimpleFilter(Filter filter, RecordQueryBuilder builder, RecordSpec spec) { var name = columnNameOf(filter); var column = findColumn(spec, name); var convertedColumn = new AsRecordColumn(column); @@ -305,7 +305,7 @@ void fieldMask(RecordQueryBuilder builder, ResponseFormat format) { private static void orderByAndLimit(RecordQueryBuilder builder, - RecordSpec spec, + RecordSpec spec, ResponseFormat format) { var hasOrderBy = false; for (var protoOrderBy : format.getOrderByList()) { @@ -331,7 +331,7 @@ void orderByAndLimit(RecordQueryBuilder builder, } private static Column - findColumn(RecordSpec spec, ColumnName columnName) { + findColumn(RecordSpec spec, ColumnName columnName) { var maybeColumn = spec.findColumn(columnName); checkArgument(maybeColumn.isPresent(), "Cannot find the column `%s` for the type `%s`.", diff --git a/server/src/main/java/io/spine/server/storage/RecordSpec.java b/server/src/main/java/io/spine/server/storage/RecordSpec.java index 60bd48e6f0..9ba9c5aa36 100644 --- a/server/src/main/java/io/spine/server/storage/RecordSpec.java +++ b/server/src/main/java/io/spine/server/storage/RecordSpec.java @@ -26,52 +26,115 @@ package io.spine.server.storage; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.Immutable; import com.google.protobuf.Message; import io.spine.annotation.SPI; import io.spine.query.Column; import io.spine.query.ColumnName; +import io.spine.query.RecordColumn; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.HashMap; import java.util.Map; import java.util.Optional; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static com.google.common.collect.Streams.stream; import static io.spine.util.Exceptions.newIllegalArgumentException; /** * Defines the specification of a record in a storage. * - *

Enumerates the record columns to store along with the record itself. + *

Defines the identifier column and the collection of the data columns to store along with + * the message record for further querying. Each column defines a way to calculate the stored value + * basing on the passed message. + * + *

In case an instance of {@code EntityRecord} is described by this spec, + * a special set of accessors for ID and Entity state columns is set. + * This is done via {@link io.spine.server.entity.storage.SpecScanner SpecScanner} + * which perform analysis of Entity state, and creates getters for properties, + * considering that an instance of {@code Any} (being {@code EntityRecord.state} field) + * must be unpacked first. * * @param * the type of the record identifier * @param * the type of the stored record - * @param - * the type of the source object on top of which the values of the columns are extracted */ @SPI -public abstract class RecordSpec { +public final class RecordSpec { + /** + * Type of stored record. + */ private final Class storedType; + + /** + * Type of record identifier. + */ private final Class idType; /** - * Creates a new {@code RecordSpec} instance for the record of the passed type. + * A method object to extract the record identifier, once such a record is passed. + */ + private final ExtractId extractId; + + /** + * The columns to store along with the record itself. + */ + private final ImmutableMap> columns; + + /** + * Creates a new record specification listing the columns to store along with the record. * - * @param idType the type of the record identifiers - * @param storedType the type of the record + * @param idType + * the type of the record identifier + * @param recordType + * the type of the record + * @param extractId + * a method object to extract the value of an identifier given an instance of a record + * @param columns + * the definitions of the columns to store along with the record */ - protected RecordSpec(Class idType, Class storedType) { - this.idType = idType; - this.storedType = storedType; + public RecordSpec(Class idType, + Class recordType, + ExtractId extractId, + Iterable> columns) { + this.idType = checkNotNull(idType); + this.storedType = checkNotNull(recordType); + this.extractId = checkNotNull(extractId); + checkNotNull(columns); + this.columns = + stream(columns).collect( + toImmutableMap(RecordColumn::name, (c) -> c) + ); } /** - * Returns the type of object, serving as the original source for the stored record - * of type {@code R}. + * Creates a new record specification. + * + *

The specifications created implies that no columns are stored for the record. + * To define the stored columns, + * please use {@linkplain #RecordSpec(Class, Class, ExtractId, Iterable) + * another ctor}. + * + * @param idType + * the type of the record identifier + * @param recordType + * the type of the record + * @param extractId + * a method object to extract the value of an identifier given an instance of a record */ - public abstract Class sourceType(); + public RecordSpec(Class idType, Class recordType, ExtractId extractId) { + this(idType, recordType, extractId, ImmutableList.of()); + } /** * Returns the type of the stored record. @@ -87,15 +150,6 @@ public final Class idType() { return idType; } - /** - * Reads the values of all columns specified for the record from the passed source. - * - * @param source - * the object from which the column values are read - * @return {@code Map} of column names and their respective values - */ - protected abstract Map valuesIn(S source); - /** * Reads the identifier value of the record. * @@ -106,7 +160,12 @@ public final Class idType() { * implementations provided outside of this module, such as Spine storage * factory on top of Google Datastore. */ - public abstract I idValueIn(S source); + public I idValueIn(R source) { + checkNotNull(source); + return extractId.apply(source); + } + + // TODO:alex.tymchenko:2023-11-03: deduplicate? /** * Extracts the identifier value from the record of a compatible type. @@ -115,7 +174,40 @@ public final Class idType() { * the record containing the ID to extract * @return the value of record identifier */ - public abstract I idFromRecord(R record); + public I idFromRecord(R record) { + return idValueIn(record); + } + + /** + * Returns the definitions of the record columns set by this specification. + */ + public ImmutableSet> columns() { + return ImmutableSet.copyOf(columns.values()); + } + + /** + * Returns the total number of columns in this specification. + */ + @VisibleForTesting + public int columnCount() { + return columns.size(); + } + + /** + * Reads the values of all columns specified for the record from the passed source. + * + * @param record + * the object from which the column values are read + * @return {@code Map} of column names and their respective values + */ + public Map valuesIn(R record) { + checkNotNull(record); + Map result = new HashMap<>(); + columns.forEach( + (name, column) -> result.put(name, column.valueIn(record)) + ); + return result; + } /** * Finds the column in this specification by the column name. @@ -125,12 +217,21 @@ public final Class idType() { * @return the column wrapped into {@code Optional}, * or {@code Optional.empty()} if no column is found */ - public abstract Optional> findColumn(ColumnName name); + public Optional> findColumn(ColumnName name) { + checkNotNull(name); + var result = columns.get(name); + return Optional.ofNullable(result); + } + + // TODO:alex.tymchenko:2023-11-03: kill? /** - * Returns the definitions of the record columns set by this specification. + * Returns the type of object, serving as the original source for the stored record + * of type {@code R}. */ - public abstract ImmutableSet> columns(); + public Class sourceType() { + return storedType(); + } /** * Finds the column in this specification by the column name. @@ -149,4 +250,31 @@ public final Class idType() { "Cannot find the column `%s` in the record specification of type `%s`.", name, storedType)); } + + /** + * A method object to extract the value of a record identifier given an instance of a record. + * + *

Once some storage is passed a record to store, the value of the record identifier has + * to be determined. To avoid passing the ID value for each record, one defines an way + * to obtain the identifier value from the record instance itself — by defining + * an {@code ExtractId} as a part of the record specification for the storage. + * + * @param + * the type of records from which to extract the ID value + * @param + * the type of the record identifiers to retrieve + */ + @Immutable + @FunctionalInterface + public interface ExtractId extends Function { + + /** + * {@inheritDoc} + * + * This method differs from its parent by the fact it never returns {@code null}. + */ + @Override + @CanIgnoreReturnValue + I apply(@Nullable R input); + } } diff --git a/server/src/main/java/io/spine/server/storage/RecordStorage.java b/server/src/main/java/io/spine/server/storage/RecordStorage.java index 64ca7ad4dc..4deafad82d 100644 --- a/server/src/main/java/io/spine/server/storage/RecordStorage.java +++ b/server/src/main/java/io/spine/server/storage/RecordStorage.java @@ -56,7 +56,7 @@ @SuppressWarnings("ClassWithTooManyMethods") // This is a centerpiece. public abstract class RecordStorage extends AbstractStorage { - private final MessageRecordSpec recordSpec; + private final RecordSpec recordSpec; /** * Creates the new storage instance. @@ -66,7 +66,7 @@ public abstract class RecordStorage extends AbstractStorag * @param recordSpec * definitions of the columns to store along with each record */ - protected RecordStorage(ContextSpec context, MessageRecordSpec recordSpec) { + protected RecordStorage(ContextSpec context, RecordSpec recordSpec) { super(context.isMultitenant()); this.recordSpec = recordSpec; } @@ -325,7 +325,7 @@ public RecordQueryBuilder queryBuilder() { * Returns the specification of the record format, in which the message record should be stored. */ @Internal - protected MessageRecordSpec recordSpec() { + protected RecordSpec recordSpec() { return recordSpec; } diff --git a/server/src/main/java/io/spine/server/storage/RecordStorageDelegate.java b/server/src/main/java/io/spine/server/storage/RecordStorageDelegate.java index 9cff492f84..ad9154c13d 100644 --- a/server/src/main/java/io/spine/server/storage/RecordStorageDelegate.java +++ b/server/src/main/java/io/spine/server/storage/RecordStorageDelegate.java @@ -188,7 +188,7 @@ protected boolean deleteRecord(I id) { @Override @Internal - protected MessageRecordSpec recordSpec() { + protected RecordSpec recordSpec() { return delegate.recordSpec(); } diff --git a/server/src/main/java/io/spine/server/storage/RecordWithColumns.java b/server/src/main/java/io/spine/server/storage/RecordWithColumns.java index 344223b34b..1b2c1f97b9 100644 --- a/server/src/main/java/io/spine/server/storage/RecordWithColumns.java +++ b/server/src/main/java/io/spine/server/storage/RecordWithColumns.java @@ -73,11 +73,11 @@ protected RecordWithColumns(I identifier, R record, Map stor } /** - * Creates a new record extracting the column values from the passed {@code Message} and setting - * the passed identifier value as the record identifier. + * Creates a new record extracting the column values from the passed record, + * and setting the passed identifier value as the record identifier. */ public static - RecordWithColumns create(I identifier, R record, RecordSpec recordSpec) { + RecordWithColumns create(I identifier, R record, RecordSpec recordSpec) { checkNotNull(identifier); checkNotNull(record); checkNotNull(recordSpec); @@ -86,10 +86,11 @@ RecordWithColumns create(I identifier, R record, RecordSpec recor } /** - * Creates a new record extracting the column values from the passed {@code Message}. + * Creates a new record-with-columns, + * filling the column values by extracting them from the passed record. */ public static - RecordWithColumns create(R record, RecordSpec recordSpec) { + RecordWithColumns create(R record, RecordSpec recordSpec) { checkNotNull(record); checkNotNull(recordSpec); var storageFields = recordSpec.valuesIn(record); diff --git a/server/src/main/java/io/spine/server/storage/StorageFactory.java b/server/src/main/java/io/spine/server/storage/StorageFactory.java index 4d6e183b77..e8d10627a4 100644 --- a/server/src/main/java/io/spine/server/storage/StorageFactory.java +++ b/server/src/main/java/io/spine/server/storage/StorageFactory.java @@ -97,7 +97,7 @@ public interface StorageFactory extends AutoCloseable { * to create a private instance of a record storage. */ RecordStorage - createRecordStorage(ContextSpec context, MessageRecordSpec recordSpec); + createRecordStorage(ContextSpec context, RecordSpec recordSpec); /** * Creates a new {@link AggregateStorage}. diff --git a/server/src/main/java/io/spine/server/storage/memory/InMemoryRecordStorage.java b/server/src/main/java/io/spine/server/storage/memory/InMemoryRecordStorage.java index cf02c8573e..4607f7cea7 100644 --- a/server/src/main/java/io/spine/server/storage/memory/InMemoryRecordStorage.java +++ b/server/src/main/java/io/spine/server/storage/memory/InMemoryRecordStorage.java @@ -29,7 +29,7 @@ import com.google.protobuf.Message; import io.spine.query.RecordQuery; import io.spine.server.ContextSpec; -import io.spine.server.storage.MessageRecordSpec; +import io.spine.server.storage.RecordSpec; import io.spine.server.storage.RecordStorage; import io.spine.server.storage.RecordWithColumns; @@ -47,7 +47,7 @@ public class InMemoryRecordStorage extends RecordStorage> multitenantStorage; - InMemoryRecordStorage(ContextSpec context, MessageRecordSpec recordSpec) { + InMemoryRecordStorage(ContextSpec context, RecordSpec recordSpec) { super(context, recordSpec); this.multitenantStorage = new MultitenantStorage<>(context.isMultitenant()) { diff --git a/server/src/main/java/io/spine/server/storage/memory/InMemoryStorageFactory.java b/server/src/main/java/io/spine/server/storage/memory/InMemoryStorageFactory.java index dc0e849b9e..b71d1f9a85 100644 --- a/server/src/main/java/io/spine/server/storage/memory/InMemoryStorageFactory.java +++ b/server/src/main/java/io/spine/server/storage/memory/InMemoryStorageFactory.java @@ -28,7 +28,7 @@ import com.google.protobuf.Message; import io.spine.server.ContextSpec; -import io.spine.server.storage.MessageRecordSpec; +import io.spine.server.storage.RecordSpec; import io.spine.server.storage.StorageFactory; /** @@ -50,7 +50,7 @@ private InMemoryStorageFactory() { @Override public InMemoryRecordStorage - createRecordStorage(ContextSpec context, MessageRecordSpec spec) { + createRecordStorage(ContextSpec context, RecordSpec spec) { return new InMemoryRecordStorage<>(context, spec); } diff --git a/server/src/main/java/io/spine/server/storage/system/SystemAwareStorageFactory.java b/server/src/main/java/io/spine/server/storage/system/SystemAwareStorageFactory.java index c1804fdd4a..a8381ee1ff 100644 --- a/server/src/main/java/io/spine/server/storage/system/SystemAwareStorageFactory.java +++ b/server/src/main/java/io/spine/server/storage/system/SystemAwareStorageFactory.java @@ -37,7 +37,7 @@ import io.spine.server.delivery.InboxStorage; import io.spine.server.event.EventStore; import io.spine.server.event.store.EmptyEventStore; -import io.spine.server.storage.MessageRecordSpec; +import io.spine.server.storage.RecordSpec; import io.spine.server.storage.RecordStorage; import io.spine.server.storage.StorageFactory; @@ -116,7 +116,7 @@ public EventStore createEventStore(ContextSpec context) { @Override public RecordStorage - createRecordStorage(ContextSpec context, MessageRecordSpec recordSpec) { + createRecordStorage(ContextSpec context, RecordSpec recordSpec) { return delegate.createRecordStorage(context, recordSpec); } diff --git a/server/src/main/java/io/spine/server/tenant/DefaultTenantStorage.java b/server/src/main/java/io/spine/server/tenant/DefaultTenantStorage.java index ff0264f11e..f83c9dfb64 100644 --- a/server/src/main/java/io/spine/server/tenant/DefaultTenantStorage.java +++ b/server/src/main/java/io/spine/server/tenant/DefaultTenantStorage.java @@ -29,7 +29,7 @@ import io.spine.base.Time; import io.spine.core.TenantId; import io.spine.server.ContextSpec; -import io.spine.server.storage.MessageRecordSpec; +import io.spine.server.storage.RecordSpec; import io.spine.server.storage.StorageFactory; /** @@ -50,8 +50,8 @@ final class DefaultTenantStorage extends TenantStorage { } @SuppressWarnings("ConstantConditions") // Protobuf getters do not return {@code null}s. - private static MessageRecordSpec spec() { - return new MessageRecordSpec<>(TenantId.class, Tenant.class, Tenant::getId); + private static RecordSpec spec() { + return new RecordSpec<>(TenantId.class, Tenant.class, Tenant::getId); } @Override diff --git a/server/src/test/java/io/spine/server/entity/storage/AssertColumns.java b/server/src/test/java/io/spine/server/entity/storage/AssertColumns.java index a2e2fc9d36..b630ea486a 100644 --- a/server/src/test/java/io/spine/server/entity/storage/AssertColumns.java +++ b/server/src/test/java/io/spine/server/entity/storage/AssertColumns.java @@ -34,7 +34,7 @@ import io.spine.query.ColumnName; import io.spine.query.EntityColumn; import io.spine.server.entity.EntityRecord; -import io.spine.server.storage.MessageRecordSpec; +import io.spine.server.storage.RecordSpec; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.truth.Truth.assertThat; @@ -59,7 +59,7 @@ static void assertContains(Iterable> columns, String colu assertThat(contains).isTrue(); } - static void assertHasLifecycleColumns(MessageRecordSpec spec) { + static void assertHasLifecycleColumns(RecordSpec spec) { var columns = spec.columns(); var names = columns.stream() .map(Column::name) diff --git a/server/src/test/java/io/spine/server/migration/mirror/given/PreparedStorageFactory.java b/server/src/test/java/io/spine/server/migration/mirror/given/PreparedStorageFactory.java index 38e4855c2e..3a720202cb 100644 --- a/server/src/test/java/io/spine/server/migration/mirror/given/PreparedStorageFactory.java +++ b/server/src/test/java/io/spine/server/migration/mirror/given/PreparedStorageFactory.java @@ -31,7 +31,7 @@ import io.spine.server.ContextSpec; import io.spine.server.entity.Entity; import io.spine.server.entity.storage.EntityRecordStorage; -import io.spine.server.storage.MessageRecordSpec; +import io.spine.server.storage.RecordSpec; import io.spine.server.storage.RecordStorage; import io.spine.server.storage.StorageFactory; import io.spine.server.storage.memory.InMemoryStorageFactory; @@ -56,8 +56,8 @@ public void close() { @Override public RecordStorage createRecordStorage( - ContextSpec context, MessageRecordSpec spec) { - return InMemoryStorageFactory.newInstance().createRecordStorage(context, spec); + ContextSpec context, RecordSpec recordSpec) { + return InMemoryStorageFactory.newInstance().createRecordStorage(context, recordSpec); } @Override diff --git a/server/src/test/java/io/spine/server/model/given/storage/ModelTestStorageFactory.java b/server/src/test/java/io/spine/server/model/given/storage/ModelTestStorageFactory.java index affcbfcca8..4485a7129a 100644 --- a/server/src/test/java/io/spine/server/model/given/storage/ModelTestStorageFactory.java +++ b/server/src/test/java/io/spine/server/model/given/storage/ModelTestStorageFactory.java @@ -29,7 +29,7 @@ import com.google.protobuf.Message; import io.spine.server.ContextSpec; import io.spine.server.delivery.InboxMessage; -import io.spine.server.storage.MessageRecordSpec; +import io.spine.server.storage.RecordSpec; import io.spine.server.storage.RecordStorage; import io.spine.server.storage.StorageFactory; @@ -51,7 +51,7 @@ public ModelTestStorageFactory(StorageFactory delegate) { @Override public RecordStorage createRecordStorage( ContextSpec context, - MessageRecordSpec recordSpec + RecordSpec recordSpec ) { var storage = delegate.createRecordStorage(context, recordSpec); if (recordSpec.storedType().equals(InboxMessage.class)) { diff --git a/server/src/test/java/io/spine/server/projection/ProjectionColumnTest.java b/server/src/test/java/io/spine/server/projection/ProjectionColumnTest.java index 00bd8e89df..f1ca79947c 100644 --- a/server/src/test/java/io/spine/server/projection/ProjectionColumnTest.java +++ b/server/src/test/java/io/spine/server/projection/ProjectionColumnTest.java @@ -33,7 +33,7 @@ import io.spine.server.entity.EntityRecord; import io.spine.server.entity.storage.SpecScanner; import io.spine.server.projection.given.SavingProjection; -import io.spine.server.storage.MessageRecordSpec; +import io.spine.server.storage.RecordSpec; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -44,7 +44,7 @@ @DisplayName("`Projection` should have columns") class ProjectionColumnTest { - private static final MessageRecordSpec recordSpec = + private static final RecordSpec recordSpec = SpecScanner.scan(SavingProjection.class); @Test diff --git a/server/src/test/java/io/spine/server/storage/MessageRecordSpecTest.java b/server/src/test/java/io/spine/server/storage/MessageRecordSpecTest.java index d2131fd6b0..f907fb0ca7 100644 --- a/server/src/test/java/io/spine/server/storage/MessageRecordSpecTest.java +++ b/server/src/test/java/io/spine/server/storage/MessageRecordSpecTest.java @@ -66,9 +66,9 @@ import static io.spine.testing.DisplayNames.NOT_ACCEPT_NULLS; import static org.junit.jupiter.api.Assertions.assertThrows; -@DisplayName("`MessageRecordSpec` should") +@DisplayName("`RecordSpec` should") @SuppressWarnings("DuplicateStringLiteralInspection") /* Similar tests have similar names. */ -class MessageRecordSpecTest { +class RecordSpecTest { @Test @DisplayName(NOT_ACCEPT_NULLS) @@ -90,7 +90,7 @@ void obtainByName() { } @SuppressWarnings("OptionalGetWithoutIsPresent" /* Checked via an assertion. */) - private void assertColumn(MessageRecordSpec spec, + private void assertColumn(RecordSpec spec, RecordColumn column) { var found = spec.findColumn(column.name()); assertThat(found).isPresent(); diff --git a/server/src/test/java/io/spine/server/storage/given/EntityRecordStorageTestEnv.java b/server/src/test/java/io/spine/server/storage/given/EntityRecordStorageTestEnv.java index 87ae5a4b27..ba41559a07 100644 --- a/server/src/test/java/io/spine/server/storage/given/EntityRecordStorageTestEnv.java +++ b/server/src/test/java/io/spine/server/storage/given/EntityRecordStorageTestEnv.java @@ -43,7 +43,7 @@ import io.spine.server.entity.storage.EntityRecordStorage; import io.spine.server.entity.storage.SpecScanner; import io.spine.server.entity.storage.given.TaskViewProjection; -import io.spine.server.storage.MessageRecordSpec; +import io.spine.server.storage.RecordSpec; import io.spine.server.storage.RecordWithColumns; import io.spine.test.entity.TaskView; import io.spine.test.entity.TaskViewId; @@ -75,7 +75,7 @@ public final class EntityRecordStorageTestEnv { private EntityRecordStorageTestEnv() { } - private static final MessageRecordSpec spec = + private static final RecordSpec spec = SpecScanner.scan(TestCounterEntity.class); /** @@ -160,7 +160,7 @@ public static void assertQueryHasSingleResult( return ImmutableSet.of(name(), estimateInDays(), status(), dueDate()); } - public static MessageRecordSpec taskViewSpec() { + public static RecordSpec taskViewSpec() { return SpecScanner.scan(TaskViewProjection.class); } diff --git a/server/src/test/java/io/spine/server/storage/given/GivenStorageProject.java b/server/src/test/java/io/spine/server/storage/given/GivenStorageProject.java index 5ef28296e6..18ef89ab68 100644 --- a/server/src/test/java/io/spine/server/storage/given/GivenStorageProject.java +++ b/server/src/test/java/io/spine/server/storage/given/GivenStorageProject.java @@ -37,7 +37,7 @@ import io.spine.query.RecordColumns; import io.spine.server.entity.EntityRecord; import io.spine.server.entity.storage.SpecScanner; -import io.spine.server.storage.MessageRecordSpec; +import io.spine.server.storage.RecordSpec; import io.spine.server.storage.RecordWithColumns; import io.spine.test.storage.StgProject; import io.spine.test.storage.StgProject.Status; @@ -53,13 +53,13 @@ public final class GivenStorageProject { - private static final MessageRecordSpec messageSpec = - new MessageRecordSpec<>(StgProjectId.class, - StgProject.class, - StgProject::getId, - StgProjectColumns.definitions()); + private static final RecordSpec messageSpec = + new RecordSpec<>(StgProjectId.class, + StgProject.class, + StgProject::getId, + StgProjectColumns.definitions()); - private static final MessageRecordSpec entityRecordSpec = + private static final RecordSpec entityRecordSpec = SpecScanner.scan(StgProjectId.class, StgProject.class); /** @@ -133,7 +133,7 @@ public static StgProject newState() { /** * Returns the record specification for {@code StgProject}. */ - public static MessageRecordSpec messageSpec() { + public static RecordSpec messageSpec() { return messageSpec; } diff --git a/server/src/test/java/io/spine/server/storage/given/StgProjectStorage.java b/server/src/test/java/io/spine/server/storage/given/StgProjectStorage.java index c3793c8f17..9ae1e4dadd 100644 --- a/server/src/test/java/io/spine/server/storage/given/StgProjectStorage.java +++ b/server/src/test/java/io/spine/server/storage/given/StgProjectStorage.java @@ -27,7 +27,7 @@ package io.spine.server.storage.given; import io.spine.server.ContextSpec; -import io.spine.server.storage.MessageRecordSpec; +import io.spine.server.storage.RecordSpec; import io.spine.server.storage.RecordStorageUnderTest; import io.spine.server.storage.StorageFactory; import io.spine.server.storage.given.GivenStorageProject.StgProjectColumns; @@ -51,9 +51,9 @@ public StgProjectStorage(ContextSpec context, StorageFactory factory) { super(context, factory.createRecordStorage(context, spec())); } - private static MessageRecordSpec spec() { + private static RecordSpec spec() { @SuppressWarnings("ConstantConditions") // Proto getters return non-{@code null} values. - var spec = new MessageRecordSpec<>(StgProjectId.class, StgProject.class, StgProject::getId, + var spec = new RecordSpec<>(StgProjectId.class, StgProject.class, StgProject::getId, StgProjectColumns.definitions()); return spec; } diff --git a/server/src/test/java/io/spine/server/storage/system/SystemAwareStorageFactoryTest.java b/server/src/test/java/io/spine/server/storage/system/SystemAwareStorageFactoryTest.java index 7602dc8641..3731d3898c 100644 --- a/server/src/test/java/io/spine/server/storage/system/SystemAwareStorageFactoryTest.java +++ b/server/src/test/java/io/spine/server/storage/system/SystemAwareStorageFactoryTest.java @@ -34,7 +34,7 @@ import io.spine.server.ContextSpec; import io.spine.server.ServerEnvironment; import io.spine.server.event.store.EmptyEventStore; -import io.spine.server.storage.MessageRecordSpec; +import io.spine.server.storage.RecordSpec; import io.spine.server.storage.StorageFactory; import io.spine.server.storage.memory.InMemoryStorageFactory; import io.spine.server.storage.system.given.MemoizingStorageFactory; @@ -116,8 +116,8 @@ void delegateRecordStorage() { var factory = new MemoizingStorageFactory(); var systemAware = SystemAwareStorageFactory.wrap(factory); var recordType = Project.class; - var spec = new MessageRecordSpec<>(ProjectId.class, Project.class, - i -> ProjectId.getDefaultInstance()); + var spec = new RecordSpec<>(ProjectId.class, Project.class, + i -> ProjectId.getDefaultInstance()); var storage = systemAware.createRecordStorage(CONTEXT, spec); assertThat(storage).isNull(); assertThat(factory.requestedStorages()) diff --git a/server/src/test/java/io/spine/server/storage/system/given/MemoizingStorageFactory.java b/server/src/test/java/io/spine/server/storage/system/given/MemoizingStorageFactory.java index 0b76f2567d..ce79d64bd4 100644 --- a/server/src/test/java/io/spine/server/storage/system/given/MemoizingStorageFactory.java +++ b/server/src/test/java/io/spine/server/storage/system/given/MemoizingStorageFactory.java @@ -35,7 +35,7 @@ import io.spine.server.delivery.CatchUpStorage; import io.spine.server.delivery.InboxStorage; import io.spine.server.event.EventStore; -import io.spine.server.storage.MessageRecordSpec; +import io.spine.server.storage.RecordSpec; import io.spine.server.storage.RecordStorage; import io.spine.server.storage.StorageFactory; @@ -83,7 +83,7 @@ public EventStore createEventStore(ContextSpec context) { @Override public RecordStorage - createRecordStorage(ContextSpec context, MessageRecordSpec recordSpec) { + createRecordStorage(ContextSpec context, RecordSpec recordSpec) { requestedStorages.add(recordSpec.storedType()); return nullRef(); } From a3fe5760e7bc73739e2e818e6a2af79a7dd5f715 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 3 Nov 2023 16:05:44 +0000 Subject: [PATCH 39/55] Revise the API of `RecordSpec` and remove semantic duplicates. --- .../io/spine/server/storage/RecordSpec.java | 32 ++----------------- .../server/storage/MessageRecordSpecTest.java | 4 +-- 2 files changed, 5 insertions(+), 31 deletions(-) diff --git a/server/src/main/java/io/spine/server/storage/RecordSpec.java b/server/src/main/java/io/spine/server/storage/RecordSpec.java index 9ba9c5aa36..fb9b03ac00 100644 --- a/server/src/main/java/io/spine/server/storage/RecordSpec.java +++ b/server/src/main/java/io/spine/server/storage/RecordSpec.java @@ -139,14 +139,14 @@ public RecordSpec(Class idType, Class recordType, ExtractId extractI /** * Returns the type of the stored record. */ - public final Class storedType() { + public Class storedType() { return storedType; } /** * Returns the type of the record identifiers. */ - public final Class idType() { + public Class idType() { return idType; } @@ -156,28 +156,12 @@ public final Class idType() { * @param source * the object providing the ID value * @return the value of the identifier - * @apiNote This method is made {@code public} in order to be accessible to storage - * implementations provided outside of this module, such as Spine storage - * factory on top of Google Datastore. */ public I idValueIn(R source) { checkNotNull(source); return extractId.apply(source); } - // TODO:alex.tymchenko:2023-11-03: deduplicate? - - /** - * Extracts the identifier value from the record of a compatible type. - * - * @param record - * the record containing the ID to extract - * @return the value of record identifier - */ - public I idFromRecord(R record) { - return idValueIn(record); - } - /** * Returns the definitions of the record columns set by this specification. */ @@ -223,16 +207,6 @@ public int columnCount() { return Optional.ofNullable(result); } - // TODO:alex.tymchenko:2023-11-03: kill? - - /** - * Returns the type of object, serving as the original source for the stored record - * of type {@code R}. - */ - public Class sourceType() { - return storedType(); - } - /** * Finds the column in this specification by the column name. * @@ -244,7 +218,7 @@ public Class sourceType() { * @throws IllegalArgumentException * if the column is not found */ - public final Column get(ColumnName name) throws IllegalArgumentException { + public Column get(ColumnName name) throws IllegalArgumentException { return findColumn(name) .orElseThrow(() -> newIllegalArgumentException( "Cannot find the column `%s` in the record specification of type `%s`.", diff --git a/server/src/test/java/io/spine/server/storage/MessageRecordSpecTest.java b/server/src/test/java/io/spine/server/storage/MessageRecordSpecTest.java index f907fb0ca7..401a74a605 100644 --- a/server/src/test/java/io/spine/server/storage/MessageRecordSpecTest.java +++ b/server/src/test/java/io/spine/server/storage/MessageRecordSpecTest.java @@ -110,7 +110,7 @@ void returnAllColumns() { void returnIdValue() { var id = newId(); var project = newState(id); - var actual = messageSpec().idFromRecord(project); + var actual = messageSpec().idValueIn(project); assertThat(actual).isEqualTo(id); } } @@ -214,7 +214,7 @@ var record = newEntityRecord(state, archived, deleted, version); status().name(), state.getStatus(), dueDate().name(), state.getDueDate() ); - var actual = spec.idFromRecord(record); + var actual = spec.idValueIn(record); assertThat(actual) .isEqualTo(state.getId()); } From ac4cdc7be6e1f0ef6bd0bf91a7f681ed3beaecd3 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Sat, 4 Nov 2023 14:22:23 +0000 Subject: [PATCH 40/55] Make `RecordSpec` remember the type of Entity state. Document the difference between `recordType()` and `sourceType()`. Rename `MessageRecordSpecTest.java` into `RecordSpecTest.java`. --- .../server/entity/storage/SpecScanner.java | 13 ++-- .../spine/server/storage/QueryConverter.java | 6 +- .../io/spine/server/storage/RecordSpec.java | 63 ++++++++++++++++--- .../spine/server/storage/RecordStorage.java | 2 +- .../storage/ModelTestStorageFactory.java | 2 +- ...ecordSpecTest.java => RecordSpecTest.java} | 14 ++++- .../system/given/MemoizingStorageFactory.java | 2 +- 7 files changed, 82 insertions(+), 20 deletions(-) rename server/src/test/java/io/spine/server/storage/{MessageRecordSpecTest.java => RecordSpecTest.java} (96%) diff --git a/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java b/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java index 07e1f7b2cb..9f68de6ea4 100644 --- a/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java +++ b/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java @@ -76,7 +76,6 @@ * parameters code-generated for each {@code EntityColumn}. */ @Internal -@SuppressWarnings("Immutable") //// TODO:alex.tymchenko:2023-10-28: address! public final class SpecScanner { /** @@ -138,7 +137,11 @@ private SpecScanner() { accumulator.add(EntityRecordColumn.version); var result = new RecordSpec<>( - idClass, EntityRecord.class, idFromRecord(), ImmutableSet.copyOf(accumulator) + idClass, + EntityRecord.class, + stateClass, + idFromRecord(), + ImmutableSet.copyOf(accumulator) ); return result; } @@ -205,8 +208,9 @@ Class castObject(Column stateCol) { * such as determining the column values. * * @param - * type of entit + * type of entity identifier * @param + * type of entity state */ private static final class MemoizingUnpacker> { @@ -229,7 +233,8 @@ private synchronized S process(Any value) { } } - @SuppressWarnings("ReturnOfNull" /* By design. */) + @SuppressWarnings({"ReturnOfNull" /* By design. */, + "Immutable" /* Unpacker and state column are effectively immutable for given state. */}) private static > Getter getter(Column stateColumn, MemoizingUnpacker unpacker) { diff --git a/server/src/main/java/io/spine/server/storage/QueryConverter.java b/server/src/main/java/io/spine/server/storage/QueryConverter.java index de249ef7f4..731ea0d01d 100644 --- a/server/src/main/java/io/spine/server/storage/QueryConverter.java +++ b/server/src/main/java/io/spine/server/storage/QueryConverter.java @@ -87,7 +87,7 @@ private QueryConverter() { checkNotNull(format); var idType = spec.idType(); - var recordType = spec.storedType(); + var recordType = spec.recordType(); var builder = RecordQuery.newBuilder(idType, recordType); identifiers(builder, filters.getIdFilter()); @@ -120,7 +120,7 @@ private QueryConverter() { checkNotNull(format); var idType = spec.idType(); - var recordType = spec.storedType(); + var recordType = spec.recordType(); var builder = RecordQuery.newBuilder(idType, recordType); fieldMask(builder, format); orderByAndLimit(builder, spec, format); @@ -335,7 +335,7 @@ void orderByAndLimit(RecordQueryBuilder builder, var maybeColumn = spec.findColumn(columnName); checkArgument(maybeColumn.isPresent(), "Cannot find the column `%s` for the type `%s`.", - columnName, spec.storedType()); + columnName, spec.recordType()); var column = maybeColumn.get(); return column; } diff --git a/server/src/main/java/io/spine/server/storage/RecordSpec.java b/server/src/main/java/io/spine/server/storage/RecordSpec.java index fb9b03ac00..7e1cd8155e 100644 --- a/server/src/main/java/io/spine/server/storage/RecordSpec.java +++ b/server/src/main/java/io/spine/server/storage/RecordSpec.java @@ -34,6 +34,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.Immutable; import com.google.protobuf.Message; +import io.spine.annotation.Internal; import io.spine.annotation.SPI; import io.spine.query.Column; import io.spine.query.ColumnName; @@ -68,13 +69,23 @@ * @param * the type of the stored record */ -@SPI public final class RecordSpec { /** * Type of stored record. */ - private final Class storedType; + private final Class recordType; + + /** + * Type of origin Proto message, which served as a source + * prior to potential transforming to a record of {@code recordType}. + * + *

If this {@code RecordSpec} describes a storage configuration + * of {@code EntityRecord}, this field is a type of corresponding Entity state. + * + *

In all other cases so far, this value equals to {@code recordType}. + */ + private final Class sourceType; /** * Type of record identifier. @@ -98,17 +109,25 @@ public final class RecordSpec { * the type of the record identifier * @param recordType * the type of the record + * @param sourceType + * the type of origin Proto message, which served as a source + * prior to potential transforming to a record of {@code recordType} * @param extractId * a method object to extract the value of an identifier given an instance of a record * @param columns * the definitions of the columns to store along with the record + * @apiNote This ctor is internal to framework, and used to create a record + * specification for Entity states stored as {@code EntityRecord}s. */ + @Internal public RecordSpec(Class idType, Class recordType, + Class sourceType, ExtractId extractId, Iterable> columns) { this.idType = checkNotNull(idType); - this.storedType = checkNotNull(recordType); + this.recordType = checkNotNull(recordType); + this.sourceType = checkNotNull(sourceType); this.extractId = checkNotNull(extractId); checkNotNull(columns); this.columns = @@ -117,6 +136,25 @@ public RecordSpec(Class idType, ); } + /** + * Creates a new record specification listing the columns to store along with the record. + * + * @param idType + * the type of the record identifier + * @param recordType + * the type of the record + * @param extractId + * a method object to extract the value of an identifier given an instance of a record + * @param columns + * the definitions of the columns to store along with the record + */ + public RecordSpec(Class idType, + Class recordType, + ExtractId extractId, + Iterable> columns) { + this(idType, recordType, recordType, extractId, columns); + } + /** * Creates a new record specification. * @@ -139,8 +177,19 @@ public RecordSpec(Class idType, Class recordType, ExtractId extractI /** * Returns the type of the stored record. */ - public Class storedType() { - return storedType; + public Class recordType() { + return recordType; + } + + /** + * Returns the type of origin Proto message, which served as a source + * prior to potential transforming to a record of {@linkplain #recordType() record type}. + * + *

In case if {@code recordType()} is {@code EntityRecord}, + * this method returns the type of Entity state. + */ + public Class sourceType() { + return sourceType; } /** @@ -222,14 +271,14 @@ public int columnCount() { return findColumn(name) .orElseThrow(() -> newIllegalArgumentException( "Cannot find the column `%s` in the record specification of type `%s`.", - name, storedType)); + name, recordType)); } /** * A method object to extract the value of a record identifier given an instance of a record. * *

Once some storage is passed a record to store, the value of the record identifier has - * to be determined. To avoid passing the ID value for each record, one defines an way + * to be determined. To avoid passing the ID value for each record, one defines a way * to obtain the identifier value from the record instance itself — by defining * an {@code ExtractId} as a part of the record specification for the storage. * diff --git a/server/src/main/java/io/spine/server/storage/RecordStorage.java b/server/src/main/java/io/spine/server/storage/RecordStorage.java index 4deafad82d..c0f1953dcb 100644 --- a/server/src/main/java/io/spine/server/storage/RecordStorage.java +++ b/server/src/main/java/io/spine/server/storage/RecordStorage.java @@ -277,7 +277,7 @@ protected RecordQuery queryForAll() { * Creates a new query builder for the records stored in this storage. */ public RecordQueryBuilder queryBuilder() { - return RecordQuery.newBuilder(recordSpec.idType(), recordSpec().storedType()); + return RecordQuery.newBuilder(recordSpec.idType(), recordSpec().recordType()); } /** diff --git a/server/src/test/java/io/spine/server/model/given/storage/ModelTestStorageFactory.java b/server/src/test/java/io/spine/server/model/given/storage/ModelTestStorageFactory.java index 4485a7129a..129fb43f03 100644 --- a/server/src/test/java/io/spine/server/model/given/storage/ModelTestStorageFactory.java +++ b/server/src/test/java/io/spine/server/model/given/storage/ModelTestStorageFactory.java @@ -54,7 +54,7 @@ public RecordStorage createRecordStorage( RecordSpec recordSpec ) { var storage = delegate.createRecordStorage(context, recordSpec); - if (recordSpec.storedType().equals(InboxMessage.class)) { + if (recordSpec.recordType().equals(InboxMessage.class)) { return new NeverForgettingStorage<>(context, storage); } return storage; diff --git a/server/src/test/java/io/spine/server/storage/MessageRecordSpecTest.java b/server/src/test/java/io/spine/server/storage/RecordSpecTest.java similarity index 96% rename from server/src/test/java/io/spine/server/storage/MessageRecordSpecTest.java rename to server/src/test/java/io/spine/server/storage/RecordSpecTest.java index 401a74a605..a45f87397c 100644 --- a/server/src/test/java/io/spine/server/storage/MessageRecordSpecTest.java +++ b/server/src/test/java/io/spine/server/storage/RecordSpecTest.java @@ -119,9 +119,17 @@ void returnIdValue() { @DisplayName("describe storage configuration of `EntityRecord` containing Entity state, and") class WithEntityRecord { + @Test + @DisplayName("return the type of Entity state") + void typeOfEntityState() { + var actual = taskViewSpec().sourceType(); + assertThat(actual) + .isEqualTo(TaskView.class); + } + @Test @DisplayName("return all definitions of the columns") - void returnAllColumns() { + void allColumns() { var columns = taskViewSpec().columns(); var actualNames = toNames(columns); @@ -144,7 +152,7 @@ private ImmutableSet toNames(ImmutableSet> co @Test @DisplayName("return the number of columns") - void returnColumns() { + void countColumns() { var lifecycleColumnCount = EntityRecordColumn.names() .size(); var protoColumnCount = 4; // See `task_view.proto`. @@ -186,7 +194,7 @@ void throwOnColumnNotFound() { @Test @DisplayName("return empty `Optional` when searching for a non-existent column") - void returnEmptyOptionalForNonExistent() { + void emptyOptionalForMissingColumn() { var nonExistent = ColumnName.of("non-existent-column"); var result = taskViewSpec().findColumn(nonExistent); diff --git a/server/src/test/java/io/spine/server/storage/system/given/MemoizingStorageFactory.java b/server/src/test/java/io/spine/server/storage/system/given/MemoizingStorageFactory.java index ce79d64bd4..48370bc982 100644 --- a/server/src/test/java/io/spine/server/storage/system/given/MemoizingStorageFactory.java +++ b/server/src/test/java/io/spine/server/storage/system/given/MemoizingStorageFactory.java @@ -84,7 +84,7 @@ public EventStore createEventStore(ContextSpec context) { @Override public RecordStorage createRecordStorage(ContextSpec context, RecordSpec recordSpec) { - requestedStorages.add(recordSpec.storedType()); + requestedStorages.add(recordSpec.recordType()); return nullRef(); } From a943f3e08ecd2605dd6fe9f92ab8279d9525332f Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Sat, 4 Nov 2023 14:32:33 +0000 Subject: [PATCH 41/55] Bump version -> `2.0.0-SNAPSHOT.172`. --- version.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle.kts b/version.gradle.kts index ed1c8a3aa1..4d263fa644 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -29,4 +29,4 @@ * * For versions of Spine-based dependencies, please see [io.spine.internal.dependency.Spine]. */ -val versionToPublish: String by extra("2.0.0-SNAPSHOT.171") +val versionToPublish: String by extra("2.0.0-SNAPSHOT.172") From ad747851b5095f3f5d95827ddb3065571ae57261 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Sat, 4 Nov 2023 14:42:05 +0000 Subject: [PATCH 42/55] Update the report files. --- license-report.md | 24 ++++++++++++------------ pom.xml | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/license-report.md b/license-report.md index ececa9ef41..bffe6572cb 100644 --- a/license-report.md +++ b/license-report.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine:spine-client:2.0.0-SNAPSHOT.171` +# Dependencies of `io.spine:spine-client:2.0.0-SNAPSHOT.172` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -798,12 +798,12 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Nov 03 00:38:26 WET 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 **Sat Nov 04 14:39:09 WET 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:spine-core:2.0.0-SNAPSHOT.171` +# Dependencies of `io.spine:spine-core:2.0.0-SNAPSHOT.172` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -1561,12 +1561,12 @@ This report was generated on **Fri Nov 03 00:38:26 WET 2023** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Nov 03 00:38:27 WET 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 **Sat Nov 04 14:39:09 WET 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:spine-server:2.0.0-SNAPSHOT.171` +# Dependencies of `io.spine:spine-server:2.0.0-SNAPSHOT.172` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -2372,12 +2372,12 @@ This report was generated on **Fri Nov 03 00:38:27 WET 2023** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Nov 03 00:38:27 WET 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 **Sat Nov 04 14:39:10 WET 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.tools:spine-testutil-client:2.0.0-SNAPSHOT.171` +# Dependencies of `io.spine.tools:spine-testutil-client:2.0.0-SNAPSHOT.172` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -3303,12 +3303,12 @@ This report was generated on **Fri Nov 03 00:38:27 WET 2023** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Nov 03 00:38:28 WET 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 **Sat Nov 04 14:39:10 WET 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.tools:spine-testutil-core:2.0.0-SNAPSHOT.171` +# Dependencies of `io.spine.tools:spine-testutil-core:2.0.0-SNAPSHOT.172` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -4234,12 +4234,12 @@ This report was generated on **Fri Nov 03 00:38:28 WET 2023** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Nov 03 00:38:28 WET 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 **Sat Nov 04 14:39:10 WET 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.tools:spine-testutil-server:2.0.0-SNAPSHOT.171` +# Dependencies of `io.spine.tools:spine-testutil-server:2.0.0-SNAPSHOT.172` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -5213,4 +5213,4 @@ This report was generated on **Fri Nov 03 00:38:28 WET 2023** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Nov 03 00:38:29 WET 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 **Sat Nov 04 14:39:11 WET 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 3bc58567fd..0649d08148 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ all modules and does not describe the project structure per-subproject. --> io.spine spine-core-java -2.0.0-SNAPSHOT.171 +2.0.0-SNAPSHOT.172 2015 From 809a847bf66b0dd963a77e1d9e63318df09cfe8c Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 8 Nov 2023 13:52:37 +0000 Subject: [PATCH 43/55] Remove a `todo` item processed a while ago. --- server/src/main/proto/spine/system/server/mirror.proto | 3 --- 1 file changed, 3 deletions(-) diff --git a/server/src/main/proto/spine/system/server/mirror.proto b/server/src/main/proto/spine/system/server/mirror.proto index 518c01f0e5..8c4a8d2c6e 100644 --- a/server/src/main/proto/spine/system/server/mirror.proto +++ b/server/src/main/proto/spine/system/server/mirror.proto @@ -89,8 +89,5 @@ message MirrorId { // Entity columns of the associated aggregate. message EntityColumns { - // TODO:2018-09-06:dmytro.dashenkov: Support entity columns. - // https://github.com/SpineEventEngine/core-java/issues/400 - map columns = 1; } From c6075b5fc35ce31e6b04698f3eff4dda541524c4 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 8 Nov 2023 14:07:14 +0000 Subject: [PATCH 44/55] Bump version -> `2.0.0-SNAPSHOT.174`. --- version.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle.kts b/version.gradle.kts index acb81b35ef..0bf1526078 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -29,4 +29,4 @@ * * For versions of Spine-based dependencies, please see [io.spine.internal.dependency.Spine]. */ -val versionToPublish: String by extra("2.0.0-SNAPSHOT.173") +val versionToPublish: String by extra("2.0.0-SNAPSHOT.174") From c22485bb503706600a999d2d50a59b373e32ff2a Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 8 Nov 2023 15:13:22 +0000 Subject: [PATCH 45/55] Update report files. --- license-report.md | 24 ++++++++++++------------ pom.xml | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/license-report.md b/license-report.md index c73e65f348..f3ca1642f6 100644 --- a/license-report.md +++ b/license-report.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine:spine-client:2.0.0-SNAPSHOT.173` +# Dependencies of `io.spine:spine-client:2.0.0-SNAPSHOT.174` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -817,12 +817,12 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Nov 04 20:26:12 WET 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 **Wed Nov 08 14:13:56 WET 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:spine-core:2.0.0-SNAPSHOT.173` +# Dependencies of `io.spine:spine-core:2.0.0-SNAPSHOT.174` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -1591,12 +1591,12 @@ This report was generated on **Sat Nov 04 20:26:12 WET 2023** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Nov 04 20:26:13 WET 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 **Wed Nov 08 14:13:56 WET 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:spine-server:2.0.0-SNAPSHOT.173` +# Dependencies of `io.spine:spine-server:2.0.0-SNAPSHOT.174` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -2421,12 +2421,12 @@ This report was generated on **Sat Nov 04 20:26:13 WET 2023** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Nov 04 20:26:13 WET 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 **Wed Nov 08 14:13:57 WET 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.tools:spine-testutil-client:2.0.0-SNAPSHOT.173` +# Dependencies of `io.spine.tools:spine-testutil-client:2.0.0-SNAPSHOT.174` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -3358,12 +3358,12 @@ This report was generated on **Sat Nov 04 20:26:13 WET 2023** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Nov 04 20:26:14 WET 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 **Wed Nov 08 14:13:57 WET 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.tools:spine-testutil-core:2.0.0-SNAPSHOT.173` +# Dependencies of `io.spine.tools:spine-testutil-core:2.0.0-SNAPSHOT.174` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -4295,12 +4295,12 @@ This report was generated on **Sat Nov 04 20:26:14 WET 2023** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Nov 04 20:26:14 WET 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 **Wed Nov 08 14:13:57 WET 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.tools:spine-testutil-server:2.0.0-SNAPSHOT.173` +# Dependencies of `io.spine.tools:spine-testutil-server:2.0.0-SNAPSHOT.174` ## Runtime 1. **Group** : com.google.android. **Name** : annotations. **Version** : 4.1.1.4. @@ -5280,4 +5280,4 @@ This report was generated on **Sat Nov 04 20:26:14 WET 2023** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Sat Nov 04 20:26:15 WET 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 **Wed Nov 08 14:13:58 WET 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 c9eafd7bad..66f4b596f2 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ all modules and does not describe the project structure per-subproject. --> io.spine spine-core-java -2.0.0-SNAPSHOT.173 +2.0.0-SNAPSHOT.174 2015 From dfe89deb005fb1bd8f7c685f6c42f956b14ee83c Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 9 Nov 2023 12:32:40 +0000 Subject: [PATCH 46/55] Move the nested type declaration to the bottom of the enclosing type. --- .../server/entity/storage/SpecScanner.java | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java b/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java index 9f68de6ea4..73bdab0421 100644 --- a/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java +++ b/server/src/main/java/io/spine/server/entity/storage/SpecScanner.java @@ -200,39 +200,6 @@ Class castObject(Column stateCol) { return (Class) stateCol.type(); } - /** - * Unpacks Entity states from {@code Any} instances, caching the unpacked results. - * - *

This routine is used as a scoped cache for on-the-fly unpacking Entity state - * from {@code EntityRecord}s, and then passing them on to other operations, - * such as determining the column values. - * - * @param - * type of entity identifier - * @param - * type of entity state - */ - private static final class MemoizingUnpacker> { - - private final Class stateCls; - - private final Map cache = new HashMap<>(); - - private MemoizingUnpacker(Class cls) { - stateCls = cls; - } - - private synchronized S process(Any value) { - @Nullable S alreadyUnpacked = cache.get(value); - if (alreadyUnpacked != null) { - return alreadyUnpacked; - } - var state = unpack(value, stateCls); - cache.put(value, state); - return state; - } - } - @SuppressWarnings({"ReturnOfNull" /* By design. */, "Immutable" /* Unpacker and state column are effectively immutable for given state. */}) private static > @@ -308,4 +275,37 @@ public I apply(@Nullable EntityRecord input) { } return columnClass; } + + /** + * Unpacks Entity states from {@code Any} instances, caching the unpacked results. + * + *

This routine is used as a scoped cache for on-the-fly unpacking Entity state + * from {@code EntityRecord}s, and then passing them on to other operations, + * such as determining the column values. + * + * @param + * type of entity identifier + * @param + * type of entity state + */ + private static final class MemoizingUnpacker> { + + private final Class stateCls; + + private final Map cache = new HashMap<>(); + + private MemoizingUnpacker(Class cls) { + stateCls = cls; + } + + private synchronized S process(Any value) { + @Nullable S alreadyUnpacked = cache.get(value); + if (alreadyUnpacked != null) { + return alreadyUnpacked; + } + var state = unpack(value, stateCls); + cache.put(value, state); + return state; + } + } } From e37886352a690f4c3cbccd68136322f521e14b2d Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 9 Nov 2023 12:33:50 +0000 Subject: [PATCH 47/55] Improve the documentation and optimize imports. --- .../spine/server/storage/AbstractColumnMapping.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/server/src/main/java/io/spine/server/storage/AbstractColumnMapping.java b/server/src/main/java/io/spine/server/storage/AbstractColumnMapping.java index 1e724e24c8..b350732bb2 100644 --- a/server/src/main/java/io/spine/server/storage/AbstractColumnMapping.java +++ b/server/src/main/java/io/spine/server/storage/AbstractColumnMapping.java @@ -26,9 +26,6 @@ package io.spine.server.storage; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableMap; import com.google.protobuf.ByteString; import com.google.protobuf.Message; @@ -47,9 +44,9 @@ *

Since record columns are proto-based and have a limited amount of possible types, this class * allows descendants to override concrete type mapping rules in a convenient way. * - *

Some of the types are expected to be mapped in a way so they support the ordering comparison - * operators ("greater than", "less than or equals", etc.). For details, see - * {@link io.spine.client.Filters}. + *

Some of the types are expected to be mapped in a way so that + * they support the ordering comparison operators ("greater than", "less than or equals", etc.). + * For details, see {@link io.spine.client.Filters}. * * @param * the type of stored records @@ -57,7 +54,7 @@ public abstract class AbstractColumnMapping implements ColumnMapping { /** - * The mapping rules of built-in proto types. + * The mapping rules of built-in Proto types. */ private final ImmutableMap, Supplier>> standardTypesMapping @@ -229,7 +226,7 @@ protected IllegalArgumentException unsupportedType(Class aClass) { } /** - * Searches for the column type mapping among standard proto type mappings. + * Searches for the column type mapping among standard Proto type mappings. * *

Inherited types are taken into account too, so if the passed type extends {@link Enum}, * the {@linkplain #ofEnum() enum type mapping} will be used. From 2537c13dfbc130c0eea14683d41cd6679bb22ebd Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 9 Nov 2023 12:35:27 +0000 Subject: [PATCH 48/55] Suppress similar warnings. --- .../java/io/spine/server/storage/AbstractColumnMapping.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/main/java/io/spine/server/storage/AbstractColumnMapping.java b/server/src/main/java/io/spine/server/storage/AbstractColumnMapping.java index b350732bb2..5af26d3620 100644 --- a/server/src/main/java/io/spine/server/storage/AbstractColumnMapping.java +++ b/server/src/main/java/io/spine/server/storage/AbstractColumnMapping.java @@ -215,6 +215,7 @@ protected IllegalArgumentException unsupportedType(Class aClass) { *

If such mapping for the passed type exists, it will be retrieved by this method. */ private Optional> customMappingFor(Class columnType) { + @SuppressWarnings("DataFlowIssue" /* `customMapping()` is not nullable.*/) Optional> result = customMapping().keySet() .stream() @@ -232,6 +233,7 @@ protected IllegalArgumentException unsupportedType(Class aClass) { * the {@linkplain #ofEnum() enum type mapping} will be used. */ private Optional> standardMappingFor(Class columnType) { + @SuppressWarnings("DataFlowIssue" /* `standardTypesMapping.get()` has value. */) Optional> result = standardTypesMapping.keySet() .stream() From f62c9a89b2ad05f78f0b36dacc440f6054d682f6 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 9 Nov 2023 12:38:11 +0000 Subject: [PATCH 49/55] Remove `@SPI` annotations on types in favour of package-level `@SPI`. --- .../src/main/java/io/spine/server/storage/ColumnMapping.java | 2 -- .../main/java/io/spine/server/storage/ColumnTypeMapping.java | 2 -- .../src/main/java/io/spine/server/storage/MessageStorage.java | 3 --- .../src/main/java/io/spine/server/storage/QueryConverter.java | 2 -- .../src/main/java/io/spine/server/storage/RecordStorage.java | 2 -- .../main/java/io/spine/server/storage/RecordWithColumns.java | 2 -- server/src/main/java/io/spine/server/storage/Storage.java | 2 -- 7 files changed, 15 deletions(-) diff --git a/server/src/main/java/io/spine/server/storage/ColumnMapping.java b/server/src/main/java/io/spine/server/storage/ColumnMapping.java index 7380c9400b..fb246dbc45 100644 --- a/server/src/main/java/io/spine/server/storage/ColumnMapping.java +++ b/server/src/main/java/io/spine/server/storage/ColumnMapping.java @@ -26,7 +26,6 @@ package io.spine.server.storage; -import io.spine.annotation.SPI; import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -42,7 +41,6 @@ * @param * a supertype of all stored values */ -@SPI public interface ColumnMapping { /** diff --git a/server/src/main/java/io/spine/server/storage/ColumnTypeMapping.java b/server/src/main/java/io/spine/server/storage/ColumnTypeMapping.java index 7f690de7f0..829818ed2d 100644 --- a/server/src/main/java/io/spine/server/storage/ColumnTypeMapping.java +++ b/server/src/main/java/io/spine/server/storage/ColumnTypeMapping.java @@ -27,7 +27,6 @@ package io.spine.server.storage; import io.spine.annotation.Internal; -import io.spine.annotation.SPI; import java.util.function.Function; @@ -41,7 +40,6 @@ * @param * the "persist as" type */ -@SPI public interface ColumnTypeMapping extends Function { /** diff --git a/server/src/main/java/io/spine/server/storage/MessageStorage.java b/server/src/main/java/io/spine/server/storage/MessageStorage.java index 2f79c8462a..3639bd52ac 100644 --- a/server/src/main/java/io/spine/server/storage/MessageStorage.java +++ b/server/src/main/java/io/spine/server/storage/MessageStorage.java @@ -27,8 +27,6 @@ package io.spine.server.storage; import com.google.protobuf.Message; -import io.spine.annotation.Internal; -import io.spine.annotation.SPI; import io.spine.server.ContextSpec; import static com.google.common.collect.Streams.stream; @@ -54,7 +52,6 @@ * for the persisted {@code Message}s * @see io.spine.server.entity.storage.EntityRecordStorage EntityRecordStorage */ -@SPI public abstract class MessageStorage extends RecordStorageDelegate { /** diff --git a/server/src/main/java/io/spine/server/storage/QueryConverter.java b/server/src/main/java/io/spine/server/storage/QueryConverter.java index 731ea0d01d..e21a6d72e3 100644 --- a/server/src/main/java/io/spine/server/storage/QueryConverter.java +++ b/server/src/main/java/io/spine/server/storage/QueryConverter.java @@ -30,7 +30,6 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.Immutable; import com.google.protobuf.Message; -import io.spine.annotation.SPI; import io.spine.base.Identifier; import io.spine.client.CompositeFilter; import io.spine.client.Filter; @@ -59,7 +58,6 @@ /** * Converts the queries defined in Protobuf into the language of {@code io.spine.query} package. */ -@SPI public final class QueryConverter { private QueryConverter() { diff --git a/server/src/main/java/io/spine/server/storage/RecordStorage.java b/server/src/main/java/io/spine/server/storage/RecordStorage.java index c0f1953dcb..996b366019 100644 --- a/server/src/main/java/io/spine/server/storage/RecordStorage.java +++ b/server/src/main/java/io/spine/server/storage/RecordStorage.java @@ -30,7 +30,6 @@ import com.google.protobuf.FieldMask; import com.google.protobuf.Message; import io.spine.annotation.Internal; -import io.spine.annotation.SPI; import io.spine.client.ResponseFormat; import io.spine.query.RecordQuery; import io.spine.query.RecordQueryBuilder; @@ -52,7 +51,6 @@ * @param * the type of the stored message records */ -@SPI @SuppressWarnings("ClassWithTooManyMethods") // This is a centerpiece. public abstract class RecordStorage extends AbstractStorage { diff --git a/server/src/main/java/io/spine/server/storage/RecordWithColumns.java b/server/src/main/java/io/spine/server/storage/RecordWithColumns.java index 1b2c1f97b9..738e11dd05 100644 --- a/server/src/main/java/io/spine/server/storage/RecordWithColumns.java +++ b/server/src/main/java/io/spine/server/storage/RecordWithColumns.java @@ -30,7 +30,6 @@ import com.google.common.collect.ImmutableSet; import com.google.protobuf.Message; import io.spine.annotation.Internal; -import io.spine.annotation.SPI; import io.spine.query.ColumnName; import org.checkerframework.checker.nullness.qual.Nullable; @@ -52,7 +51,6 @@ * @param * the type of the stored records */ -@SPI public class RecordWithColumns { private final I id; diff --git a/server/src/main/java/io/spine/server/storage/Storage.java b/server/src/main/java/io/spine/server/storage/Storage.java index d67e2dfe10..6bf5c26d31 100644 --- a/server/src/main/java/io/spine/server/storage/Storage.java +++ b/server/src/main/java/io/spine/server/storage/Storage.java @@ -27,7 +27,6 @@ package io.spine.server.storage; import com.google.protobuf.Message; -import io.spine.annotation.SPI; import java.util.Iterator; import java.util.Optional; @@ -40,7 +39,6 @@ * @param * the type of records */ -@SPI public interface Storage extends AutoCloseable { /** From 38e99aa7d4ebad86f8c37e3f7f1963caf47aafd3 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 9 Nov 2023 12:38:41 +0000 Subject: [PATCH 50/55] Improve the documentation. --- .../src/main/java/io/spine/server/storage/QueryConverter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/io/spine/server/storage/QueryConverter.java b/server/src/main/java/io/spine/server/storage/QueryConverter.java index e21a6d72e3..2355bbf8b3 100644 --- a/server/src/main/java/io/spine/server/storage/QueryConverter.java +++ b/server/src/main/java/io/spine/server/storage/QueryConverter.java @@ -244,7 +244,7 @@ private QueryConverter() { } /** - * Converts the a single filter into a condition and adds it to the passed builder + * Converts a single filter into a condition and adds it to the passed builder * according to the record specification. */ private static void From 3672c2f6ae4793d94d479a06a0de475f72ae6e22 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 9 Nov 2023 12:39:41 +0000 Subject: [PATCH 51/55] Optimize imports. --- server/src/main/java/io/spine/server/storage/RecordSpec.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/main/java/io/spine/server/storage/RecordSpec.java b/server/src/main/java/io/spine/server/storage/RecordSpec.java index 7e1cd8155e..eb8559d582 100644 --- a/server/src/main/java/io/spine/server/storage/RecordSpec.java +++ b/server/src/main/java/io/spine/server/storage/RecordSpec.java @@ -35,7 +35,6 @@ import com.google.errorprone.annotations.Immutable; import com.google.protobuf.Message; import io.spine.annotation.Internal; -import io.spine.annotation.SPI; import io.spine.query.Column; import io.spine.query.ColumnName; import io.spine.query.RecordColumn; From c287bb4f84aeca376207cd02377a8de4b00f278e Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 9 Nov 2023 12:42:13 +0000 Subject: [PATCH 52/55] Make type `final`. --- .../server/storage/RecordWithColumns.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/server/src/main/java/io/spine/server/storage/RecordWithColumns.java b/server/src/main/java/io/spine/server/storage/RecordWithColumns.java index 738e11dd05..09408d0423 100644 --- a/server/src/main/java/io/spine/server/storage/RecordWithColumns.java +++ b/server/src/main/java/io/spine/server/storage/RecordWithColumns.java @@ -51,7 +51,7 @@ * @param * the type of the stored records */ -public class RecordWithColumns { +public final class RecordWithColumns { private final I id; private final R record; @@ -64,7 +64,7 @@ public class RecordWithColumns { */ private final Map storageFields; - protected RecordWithColumns(I identifier, R record, Map storageFields) { + private RecordWithColumns(I identifier, R record, Map storageFields) { this.id = checkNotNull(identifier); this.record = checkNotNull(record); this.storageFields = new HashMap<>(storageFields); @@ -117,14 +117,14 @@ RecordWithColumns of(I identifier, R record, Map stora /** * Returns the identifier of the record. */ - public final I id() { + public I id() { return id; } /** * Returns the message of the record. */ - public final R record() { + public R record() { return record; } @@ -133,7 +133,7 @@ public final R record() { * * @return the storage field names */ - public final ImmutableSet columnNames() { + public ImmutableSet columnNames() { return ImmutableSet.copyOf(storageFields.keySet()); } @@ -154,7 +154,7 @@ public final ImmutableSet columnNames() { * @throws IllegalStateException * if there is no column with the specified name */ - public final @Nullable Object columnValue(ColumnName columnName) { + public @Nullable Object columnValue(ColumnName columnName) { return columnValue(columnName, DefaultColumnMapping.INSTANCE); } @@ -163,7 +163,7 @@ public final ImmutableSet columnNames() { * *

The specified column mapping will be used to do the column value conversion. */ - public final V columnValue(ColumnName columnName, ColumnMapping columnMapping) { + public V columnValue(ColumnName columnName, ColumnMapping columnMapping) { checkNotNull(columnName); checkNotNull(columnMapping); if (!storageFields.containsKey(columnName)) { @@ -185,14 +185,14 @@ public final V columnValue(ColumnName columnName, ColumnMapping columnMap * Tells if there are any {@linkplain io.spine.query.Column columns} * associated with this record. */ - public final boolean hasColumns() { + public boolean hasColumns() { return !storageFields.isEmpty(); } /** * Determines if there is a column with the specified name among the storage fields. */ - public final boolean hasColumn(ColumnName name) { + public boolean hasColumn(ColumnName name) { var result = storageFields.containsKey(name); return result; } @@ -204,7 +204,7 @@ public final boolean hasColumn(ColumnName name) { * ImmutableMap} since the map values are {@code null}-able. */ @Internal - protected final Map storageFields() { + private Map storageFields() { return unmodifiableMap(storageFields); } From 38d215668bc29d946fde908414492fdd42f9a18b Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 9 Nov 2023 13:08:38 +0000 Subject: [PATCH 53/55] Revise the API of `RecordWithColumns` and cover it (explicitly) with its own tests. --- .../server/storage/RecordWithColumns.java | 17 +- .../server/storage/RecordWithColumnsTest.java | 164 ++++++++++++++++++ 2 files changed, 165 insertions(+), 16 deletions(-) create mode 100644 server/src/test/java/io/spine/server/storage/RecordWithColumnsTest.java diff --git a/server/src/main/java/io/spine/server/storage/RecordWithColumns.java b/server/src/main/java/io/spine/server/storage/RecordWithColumns.java index 09408d0423..3399b42e34 100644 --- a/server/src/main/java/io/spine/server/storage/RecordWithColumns.java +++ b/server/src/main/java/io/spine/server/storage/RecordWithColumns.java @@ -26,10 +26,8 @@ package io.spine.server.storage; -import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Message; -import io.spine.annotation.Internal; import io.spine.query.ColumnName; import org.checkerframework.checker.nullness.qual.Nullable; @@ -40,7 +38,6 @@ import static com.google.common.base.Preconditions.checkNotNull; import static io.spine.util.Exceptions.newIllegalStateException; -import static java.util.Collections.unmodifiableMap; /** * A value of some message record along with the values @@ -108,8 +105,7 @@ public static RecordWithColumns of(I id, R record) /** * Creates a new instance from the passed record and storage fields. */ - @VisibleForTesting - public static + private static RecordWithColumns of(I identifier, R record, Map storageFields) { return new RecordWithColumns<>(identifier, record, storageFields); } @@ -197,17 +193,6 @@ public boolean hasColumn(ColumnName name) { return result; } - /** - * Returns an unmodifiable copy of the values of storage fields associated with this record. - * - * @apiNote This method does not return an {@link com.google.common.collect.ImmutableMap - * ImmutableMap} since the map values are {@code null}-able. - */ - @Internal - private Map storageFields() { - return unmodifiableMap(storageFields); - } - @Override public boolean equals(Object o) { if (this == o) { diff --git a/server/src/test/java/io/spine/server/storage/RecordWithColumnsTest.java b/server/src/test/java/io/spine/server/storage/RecordWithColumnsTest.java new file mode 100644 index 0000000000..b221910f2a --- /dev/null +++ b/server/src/test/java/io/spine/server/storage/RecordWithColumnsTest.java @@ -0,0 +1,164 @@ +/* + * 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; + +import com.google.common.testing.EqualsTester; +import com.google.common.testing.NullPointerTester; +import io.spine.query.RecordColumn; +import io.spine.server.storage.given.GivenStorageProject.StgProjectColumns; +import io.spine.server.storage.given.TestColumnMapping; +import io.spine.test.storage.StgProject; +import io.spine.test.storage.StgProjectId; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static com.google.common.truth.Truth.assertThat; +import static io.spine.server.storage.given.GivenStorageProject.StgProjectColumns.due_date; +import static io.spine.server.storage.given.GivenStorageProject.StgProjectColumns.name; +import static io.spine.server.storage.given.GivenStorageProject.StgProjectColumns.random_non_stored_column; +import static io.spine.server.storage.given.GivenStorageProject.messageSpec; +import static io.spine.server.storage.given.GivenStorageProject.newState; +import static io.spine.server.storage.given.TestColumnMapping.CONVERTED_STRING; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@DisplayName("`RecordWithColumns` should") +class RecordWithColumnsTest { + + @Test + @DisplayName("not accept `null`s in static API") + void rejectNullInCtor() { + new NullPointerTester() + .testAllPublicStaticMethods(RecordWithColumns.class); + } + + @Test + @DisplayName("support record equality") + void supportEquality() { + var record = newState(); + var noFields = withNoCols(record); + var withFieldsBySpec = withCols(record); + var withFieldsByIdAndSpec = RecordWithColumns.create(record.getId(), record, messageSpec()); + new EqualsTester() + .addEqualityGroup(noFields) + .addEqualityGroup(withFieldsBySpec, withFieldsByIdAndSpec) + .testEquals(); + } + + @Test + @DisplayName("return empty names collection if no storage fields are set") + void returnEmptyColumns() { + var record = withCols(); + assertThat(record.hasColumns()).isFalse(); + + var names = record.columnNames(); + assertThat(names.isEmpty()).isTrue(); + } + + @Test + @DisplayName("throw `ISE` on attempt to get value by non-existent name") + void throwOnNonExistentColumn() { + var record = withCols(); + assertThrows(IllegalStateException.class, + () -> record.columnValue(random_non_stored_column.name())); + } + + @Test + @DisplayName("return column value by column name") + void returnColumnValue() { + var original = newState(); + var withColumns = withCols(original); + var actual = withColumns.columnValue(due_date.name()); + + assertThat(actual) + .isEqualTo(original.getDueDate()); + } + + @Test + @DisplayName("return a column value with the column mapping applied") + void returnValueWithColumnMapping() { + var original = newState(); + var withColumns = withCols(original); + var actual = withColumns.columnValue(name.name(), new TestColumnMapping()); + + assertThat(actual) + .isEqualTo(CONVERTED_STRING); + } + + @Nested + @DisplayName("return") + class Store { + + @Test + @DisplayName("original record") + void record() { + var original = newState(); + var record = withCols(original); + assertThat(record.record()) + .isEqualTo(original); + } + + @Test + @DisplayName("columns and their values") + void columnValues() { + var original = newState(); + var record = withCols(original); + + var expectedColumns = StgProjectColumns.definitions(); + var expectedNames = expectedColumns.stream() + .map(RecordColumn::name) + .collect(toImmutableSet()); + assertThat(record.columnNames()) + .isEqualTo(expectedNames); + + for (var column : expectedColumns) { + var expected = column.valueIn(original); + var actual = record.columnValue(column.name()); + assertThat(actual). + isEqualTo(expected); + } + } + } + + @NonNull + private static + RecordWithColumns withCols(StgProject original) { + return RecordWithColumns.create(original, messageSpec()); + } + + private static + RecordWithColumns withNoCols(StgProject record) { + return RecordWithColumns.of(record.getId(), record); + } + + @NonNull + private static RecordWithColumns withCols() { + return withNoCols(newState()); + } +} From 768d7f57db13471cd98d11f2ae099077de54674a Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 9 Nov 2023 13:14:39 +0000 Subject: [PATCH 54/55] Provide a default value for testing. --- .../test/java/io/spine/server/storage/RecordWithColumnsTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/test/java/io/spine/server/storage/RecordWithColumnsTest.java b/server/src/test/java/io/spine/server/storage/RecordWithColumnsTest.java index b221910f2a..df95a6f07b 100644 --- a/server/src/test/java/io/spine/server/storage/RecordWithColumnsTest.java +++ b/server/src/test/java/io/spine/server/storage/RecordWithColumnsTest.java @@ -55,6 +55,7 @@ class RecordWithColumnsTest { @DisplayName("not accept `null`s in static API") void rejectNullInCtor() { new NullPointerTester() + .setDefault(RecordSpec.class, messageSpec()) .testAllPublicStaticMethods(RecordWithColumns.class); } From e4ca6ea5ae82a3932e5b4841e034361f9c552604 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 9 Nov 2023 13:29:18 +0000 Subject: [PATCH 55/55] Move the test-env-y methods to `GivenStorageProject`. --- .../server/storage/RecordWithColumnsTest.java | 83 ++++++------------- .../storage/given/GivenStorageProject.java | 28 +++++++ 2 files changed, 55 insertions(+), 56 deletions(-) diff --git a/server/src/test/java/io/spine/server/storage/RecordWithColumnsTest.java b/server/src/test/java/io/spine/server/storage/RecordWithColumnsTest.java index df95a6f07b..2c4edaabba 100644 --- a/server/src/test/java/io/spine/server/storage/RecordWithColumnsTest.java +++ b/server/src/test/java/io/spine/server/storage/RecordWithColumnsTest.java @@ -31,20 +31,18 @@ import io.spine.query.RecordColumn; import io.spine.server.storage.given.GivenStorageProject.StgProjectColumns; import io.spine.server.storage.given.TestColumnMapping; -import io.spine.test.storage.StgProject; -import io.spine.test.storage.StgProjectId; -import org.checkerframework.checker.nullness.qual.NonNull; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.truth.Truth.assertThat; -import static io.spine.server.storage.given.GivenStorageProject.StgProjectColumns.due_date; import static io.spine.server.storage.given.GivenStorageProject.StgProjectColumns.name; import static io.spine.server.storage.given.GivenStorageProject.StgProjectColumns.random_non_stored_column; import static io.spine.server.storage.given.GivenStorageProject.messageSpec; import static io.spine.server.storage.given.GivenStorageProject.newState; +import static io.spine.server.storage.given.GivenStorageProject.withCols; +import static io.spine.server.storage.given.GivenStorageProject.withNoCols; import static io.spine.server.storage.given.TestColumnMapping.CONVERTED_STRING; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -64,24 +62,14 @@ void rejectNullInCtor() { void supportEquality() { var record = newState(); var noFields = withNoCols(record); - var withFieldsBySpec = withCols(record); - var withFieldsByIdAndSpec = RecordWithColumns.create(record.getId(), record, messageSpec()); + var withColsBySpec = withCols(record); + var withColsByIdAndSpec = RecordWithColumns.create(record.getId(), record, messageSpec()); new EqualsTester() .addEqualityGroup(noFields) - .addEqualityGroup(withFieldsBySpec, withFieldsByIdAndSpec) + .addEqualityGroup(withColsBySpec, withColsByIdAndSpec) .testEquals(); } - @Test - @DisplayName("return empty names collection if no storage fields are set") - void returnEmptyColumns() { - var record = withCols(); - assertThat(record.hasColumns()).isFalse(); - - var names = record.columnNames(); - assertThat(names.isEmpty()).isTrue(); - } - @Test @DisplayName("throw `ISE` on attempt to get value by non-existent name") void throwOnNonExistentColumn() { @@ -90,31 +78,30 @@ var record = withCols(); () -> record.columnValue(random_non_stored_column.name())); } - @Test - @DisplayName("return column value by column name") - void returnColumnValue() { - var original = newState(); - var withColumns = withCols(original); - var actual = withColumns.columnValue(due_date.name()); - - assertThat(actual) - .isEqualTo(original.getDueDate()); - } - - @Test - @DisplayName("return a column value with the column mapping applied") - void returnValueWithColumnMapping() { - var original = newState(); - var withColumns = withCols(original); - var actual = withColumns.columnValue(name.name(), new TestColumnMapping()); - - assertThat(actual) - .isEqualTo(CONVERTED_STRING); - } - @Nested @DisplayName("return") - class Store { + class Return { + + @Test + @DisplayName("empty names collection if no storage fields are set") + void emptyCols() { + var record = withCols(); + assertThat(record.hasColumns()).isFalse(); + + var names = record.columnNames(); + assertThat(names.isEmpty()).isTrue(); + } + + @Test + @DisplayName("a column value with the column mapping applied") + void applyingColumnMapping() { + var original = newState(); + var withColumns = withCols(original); + var actual = withColumns.columnValue(name.name(), new TestColumnMapping()); + + assertThat(actual) + .isEqualTo(CONVERTED_STRING); + } @Test @DisplayName("original record") @@ -146,20 +133,4 @@ var record = withCols(original); } } } - - @NonNull - private static - RecordWithColumns withCols(StgProject original) { - return RecordWithColumns.create(original, messageSpec()); - } - - private static - RecordWithColumns withNoCols(StgProject record) { - return RecordWithColumns.of(record.getId(), record); - } - - @NonNull - private static RecordWithColumns withCols() { - return withNoCols(newState()); - } } diff --git a/server/src/test/java/io/spine/server/storage/given/GivenStorageProject.java b/server/src/test/java/io/spine/server/storage/given/GivenStorageProject.java index 18ef89ab68..d94f2868da 100644 --- a/server/src/test/java/io/spine/server/storage/given/GivenStorageProject.java +++ b/server/src/test/java/io/spine/server/storage/given/GivenStorageProject.java @@ -152,6 +152,34 @@ var record = EntityRecord.newBuilder() return result; } + /** + * Generates a new {@code StgProject} and turns it + * into a new {@code RecordWithColumns}, filling the columns + * with the respective values, as per {@linkplain #messageSpec() specification}. + */ + public static RecordWithColumns withCols() { + return withNoCols(newState()); + } + + /** + * Transforms a passed project into a new {@code RecordWithColumns}, + * filling the columns with the respective values, + * as per {@linkplain #messageSpec() specification}. + */ + public static + RecordWithColumns withCols(StgProject project) { + return RecordWithColumns.create(project, messageSpec()); + } + + /** + * Transforms a passed project into a new {@code RecordWithColumns}, + * but not configuring any columns at all. + */ + public static + RecordWithColumns withNoCols(StgProject project) { + return RecordWithColumns.of(project.getId(), project); + } + /** * Columns of {@code StgProject} stored as record. */