From f4efe3264e7e998873d30f03bf1167f0ee43df56 Mon Sep 17 00:00:00 2001 From: Jakub Amanowicz Date: Tue, 31 Oct 2023 11:39:35 +0100 Subject: [PATCH] MCPODS-6531 refactor and sample unit tests layout --- build.gradle.kts | 1 + .../naksha/app/service/NakshaAppTest.java | 3 +- .../com/here/naksha/lib/hub/NakshaHub.java | 36 ++- .../lib/hub/storages/NHAdminStorage.java | 1 + .../hub/storages/NHAdminStorageReader.java | 1 + .../hub/storages/NHAdminStorageWriter.java | 1 + .../naksha/lib/hub2/EventPipelineFactory.java | 26 ++ .../lib/hub2/NakshaEventPipelineFactory.java | 55 ++++ .../com/here/naksha/lib/hub2/NakshaHub2.java | 109 +++++++ .../lib/hub2/NakshaHubConfiguration.java | 36 +++ .../naksha/lib/hub2/admin/AdminStorage.java | 117 +++++++ .../hub2/admin/AdminStorageConfiguration.java | 30 ++ .../lib/hub2/admin/PsqlAdminStorage.java | 52 +++ .../hub2/space/AdminBasedSpaceStorage.java | 103 ++++++ .../space/AdminBasedSpaceStorageReader.java | 212 ++++++++++++ .../space/AdminBasedSpaceStorageWriter.java | 148 +++++++++ .../naksha/lib/hub2/space/SpaceStorage.java | 42 +++ .../hub2/space/SpaceStorageConfiguration.java | 29 ++ .../naksha/lib/common/FeatureReaderUtil.java | 53 +++ .../lib/common/SampleNakshaContext.java | 29 ++ .../naksha/lib/common/TestFileLoader.java | 40 +++ .../here/naksha/lib/hub2/GetFeaturesTest.java | 106 ++++++ .../here/naksha/lib/hub2/NakshaHub2Test.java | 87 +++++ .../lib/hub2/admin/AdminStorageTest.java | 152 +++++++++ .../space/AdminBasedSpaceStorageTest.java | 304 ++++++++++++++++++ 25 files changed, 1763 insertions(+), 10 deletions(-) create mode 100644 here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/EventPipelineFactory.java create mode 100644 here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/NakshaEventPipelineFactory.java create mode 100644 here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/NakshaHub2.java create mode 100644 here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/NakshaHubConfiguration.java create mode 100644 here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/admin/AdminStorage.java create mode 100644 here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/admin/AdminStorageConfiguration.java create mode 100644 here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/admin/PsqlAdminStorage.java create mode 100644 here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/space/AdminBasedSpaceStorage.java create mode 100644 here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/space/AdminBasedSpaceStorageReader.java create mode 100644 here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/space/AdminBasedSpaceStorageWriter.java create mode 100644 here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/space/SpaceStorage.java create mode 100644 here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/space/SpaceStorageConfiguration.java create mode 100644 here-naksha-lib-hub/src/test/java/com/here/naksha/lib/common/FeatureReaderUtil.java create mode 100644 here-naksha-lib-hub/src/test/java/com/here/naksha/lib/common/SampleNakshaContext.java create mode 100644 here-naksha-lib-hub/src/test/java/com/here/naksha/lib/common/TestFileLoader.java create mode 100644 here-naksha-lib-hub/src/test/java/com/here/naksha/lib/hub2/GetFeaturesTest.java create mode 100644 here-naksha-lib-hub/src/test/java/com/here/naksha/lib/hub2/NakshaHub2Test.java create mode 100644 here-naksha-lib-hub/src/test/java/com/here/naksha/lib/hub2/admin/AdminStorageTest.java create mode 100644 here-naksha-lib-hub/src/test/java/com/here/naksha/lib/hub2/space/AdminBasedSpaceStorageTest.java diff --git a/build.gradle.kts b/build.gradle.kts index a1715debc..ee5fdae23 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -386,6 +386,7 @@ project(":here-naksha-lib-handlers") { implementation(postgres) testImplementation(json_assert) + testImplementation(mockito) } } //} catch (ignore: UnknownProjectException) { diff --git a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java index 25ab834e3..05bb27e90 100644 --- a/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java +++ b/here-naksha-app-service/src/test/java/com/here/naksha/app/service/NakshaAppTest.java @@ -33,6 +33,7 @@ import java.util.UUID; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.skyscreamer.jsonassert.JSONAssert; import org.skyscreamer.jsonassert.JSONCompareMode; @@ -79,7 +80,7 @@ private String getHeader(final HttpResponse response, final String header) { return (values == null) ? null : (values.size() > 1 ? values.toString() : values.get(0)); } - // @Test + @Test void tc0001_testGetStorages() throws Exception { // Test API : GET /hub/storages // 1. Load test data diff --git a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/NakshaHub.java b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/NakshaHub.java index 7eeaf02e6..6d24817c4 100644 --- a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/NakshaHub.java +++ b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/NakshaHub.java @@ -19,15 +19,23 @@ package com.here.naksha.lib.hub; import static com.here.naksha.lib.core.exceptions.UncheckedException.unchecked; -import static com.here.naksha.lib.core.util.storage.RequestHelper.*; +import static com.here.naksha.lib.core.util.storage.RequestHelper.createFeatureRequest; +import static com.here.naksha.lib.core.util.storage.RequestHelper.readFeaturesByIdsRequest; import com.here.naksha.lib.core.INaksha; import com.here.naksha.lib.core.NakshaAdminCollection; import com.here.naksha.lib.core.NakshaContext; import com.here.naksha.lib.core.NakshaVersion; import com.here.naksha.lib.core.models.naksha.Storage; -import com.here.naksha.lib.core.models.storage.*; +import com.here.naksha.lib.core.models.storage.EWriteOp; +import com.here.naksha.lib.core.models.storage.ErrorResult; +import com.here.naksha.lib.core.models.storage.IfConflict; +import com.here.naksha.lib.core.models.storage.IfExists; +import com.here.naksha.lib.core.models.storage.ReadResult; +import com.here.naksha.lib.core.models.storage.Result; import com.here.naksha.lib.core.models.storage.StorageCollection; +import com.here.naksha.lib.core.models.storage.WriteCollections; +import com.here.naksha.lib.core.models.storage.WriteOp; import com.here.naksha.lib.core.storage.IStorage; import com.here.naksha.lib.core.storage.IWriteSession; import com.here.naksha.lib.core.util.IoHelp; @@ -45,21 +53,31 @@ public class NakshaHub implements INaksha { - /** The id of default NakshaHub Config feature object */ + /** + * The id of default NakshaHub Config feature object + */ public static final @NotNull String DEF_CFG_ID = "default-config"; - /** The NakshaHub config. */ + /** + * The NakshaHub config. + */ protected final @NotNull NakshaHubConfig nakshaHubConfig; - /** Singleton instance of physical admin storage implementation */ + /** + * Singleton instance of physical admin storage implementation + */ protected final @NotNull IStorage psqlStorage; - /** Singleton instance of AdminStorage, which internally uses physical admin storage (i.e. PsqlStorage) */ + /** + * Singleton instance of AdminStorage, which internally uses physical admin storage (i.e. PsqlStorage) + */ protected final @NotNull IStorage adminStorageInstance; - /** Singleton instance of Space Storage, which is responsible to manage admin collections as spaces - * and support respective read/write operations on spaces */ + /** + * Singleton instance of Space Storage, which is responsible to manage admin collections as spaces and support respective read/write + * operations on spaces + */ protected final @NotNull IStorage spaceStorageInstance; @ApiStatus.AvailableSince(NakshaVersion.v2_0_7) public NakshaHub( - final @NotNull PsqlConfig config, + final @NotNull PsqlConfig config, // TODO Kuba - de facto adminDB config final @Nullable NakshaHubConfig customCfg, final @Nullable String configId) { // this.dataSource = new PsqlDataSource(config); diff --git a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/storages/NHAdminStorage.java b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/storages/NHAdminStorage.java index c84c1bd2c..648773c73 100644 --- a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/storages/NHAdminStorage.java +++ b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/storages/NHAdminStorage.java @@ -25,6 +25,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +// TODO, Kuba: ta klasa nic nie wnosi - delegat na IStorage public class NHAdminStorage implements IStorage { /** Singleton instance of physical admin storage implementation */ diff --git a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/storages/NHAdminStorageReader.java b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/storages/NHAdminStorageReader.java index 5ed7d8b0a..61dcf784a 100644 --- a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/storages/NHAdminStorageReader.java +++ b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/storages/NHAdminStorageReader.java @@ -28,6 +28,7 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +// TODO, Kuba: ta klasa nic nie wnosi, 1:1 delegacja IReadSession public class NHAdminStorageReader implements IReadSession { /** Current session, all read storage operations should be executed against */ diff --git a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/storages/NHAdminStorageWriter.java b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/storages/NHAdminStorageWriter.java index 52ec29588..893e737a9 100644 --- a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/storages/NHAdminStorageWriter.java +++ b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/storages/NHAdminStorageWriter.java @@ -28,6 +28,7 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +// TODO, Kuba: ta klasa nic nie wnosi, delegat wraper do IWriteSession public class NHAdminStorageWriter extends NHAdminStorageReader implements IWriteSession { /** Current session, all write storage operations should be executed against */ diff --git a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/EventPipelineFactory.java b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/EventPipelineFactory.java new file mode 100644 index 000000000..17a090a4b --- /dev/null +++ b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/EventPipelineFactory.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2017-2023 HERE Europe B.V. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ +package com.here.naksha.lib.hub2; + +import com.here.naksha.lib.core.EventPipeline; + +public interface EventPipelineFactory { + + EventPipeline eventPipeline(); +} diff --git a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/NakshaEventPipelineFactory.java b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/NakshaEventPipelineFactory.java new file mode 100644 index 000000000..9f610b0ad --- /dev/null +++ b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/NakshaEventPipelineFactory.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2017-2023 HERE Europe B.V. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ +package com.here.naksha.lib.hub2; + +import com.here.naksha.lib.core.EventPipeline; +import com.here.naksha.lib.core.INaksha; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + +public class NakshaEventPipelineFactory implements EventPipelineFactory { + + private final @NotNull INaksha naksha; + + public NakshaEventPipelineFactory(@NotNull INaksha naksha) { + this.naksha = naksha; + } + + @Override + public EventPipeline eventPipeline() { + return new EventPipeline(naksha); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + NakshaEventPipelineFactory that = (NakshaEventPipelineFactory) o; + return Objects.equals(naksha, that.naksha); + } + + @Override + public int hashCode() { + return Objects.hash(naksha); + } +} diff --git a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/NakshaHub2.java b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/NakshaHub2.java new file mode 100644 index 000000000..ec52a8c86 --- /dev/null +++ b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/NakshaHub2.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2017-2023 HERE Europe B.V. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ +package com.here.naksha.lib.hub2; + +import static com.here.naksha.lib.core.exceptions.UncheckedException.unchecked; + +import com.here.naksha.lib.core.IEventHandler; +import com.here.naksha.lib.core.INaksha; +import com.here.naksha.lib.core.NakshaAdminCollection; +import com.here.naksha.lib.core.storage.IStorage; +import com.here.naksha.lib.handlers.AuthorizationEventHandler; +import com.here.naksha.lib.handlers.IntHandlerForConfigs; +import com.here.naksha.lib.handlers.IntHandlerForEventHandlers; +import com.here.naksha.lib.handlers.IntHandlerForExtensions; +import com.here.naksha.lib.handlers.IntHandlerForSpaces; +import com.here.naksha.lib.handlers.IntHandlerForStorages; +import com.here.naksha.lib.handlers.IntHandlerForSubscriptions; +import com.here.naksha.lib.hub2.admin.AdminStorage; +import com.here.naksha.lib.hub2.space.SpaceStorage; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.jetbrains.annotations.NotNull; + +public class NakshaHub2 implements INaksha { + + private final @NotNull AdminStorage adminStorage; + + private final @NotNull SpaceStorage spaceStorage; + + NakshaHub2(@NotNull AdminStorage adminStorage, @NotNull SpaceStorage spaceStorage) { + this.adminStorage = adminStorage; + this.spaceStorage = spaceStorage; + spaceStorage.setVirtualSpaces(configureVirtualSpaces(this)); + spaceStorage.setEventPipelineFactory(new NakshaEventPipelineFactory(this)); + } + + /** + * Returns a thin wrapper above the admin-database that adds authorization and internal event handling. Basically, this allows access to + * the admin collections. + * + * @return the admin-storage. + */ + @Override + public @NotNull IStorage getAdminStorage() { + return adminStorage; + } + + /** + * Returns a virtual storage that maps spaces to collections and allows to execute requests in spaces. + * + * @return the virtual space-storage. + */ + @Override + public @NotNull IStorage getSpaceStorage() { + return spaceStorage; + } + + /** + * Returns the user defined space storage instance based on storageId as per space collection defined in Naksha admin storage. + * + * @param storageId Id of the space storage + * @return the space-storage + */ + @Override + public @NotNull IStorage getStorageById(@NotNull String storageId) { + // TODO : Add logic to retrieve Storage from Admin DB and then instantiate respective IStorage implementation + return null; + } + + private static @NotNull Map> configureVirtualSpaces(final @NotNull INaksha hub) { + final Map> adminSpaces = new HashMap<>(); + // common auth handler + final IEventHandler authHandler = new AuthorizationEventHandler(hub); + // add event handlers for each admin space + for (final String spaceId : NakshaAdminCollection.ALL) { + adminSpaces.put( + spaceId, + switch (spaceId) { + case NakshaAdminCollection.CONFIGS -> List.of(authHandler, new IntHandlerForConfigs(hub)); + case NakshaAdminCollection.SPACES -> List.of(authHandler, new IntHandlerForSpaces(hub)); + case NakshaAdminCollection.SUBSCRIPTIONS -> List.of( + authHandler, new IntHandlerForSubscriptions(hub)); + case NakshaAdminCollection.EVENT_HANDLERS -> List.of( + authHandler, new IntHandlerForEventHandlers(hub)); + case NakshaAdminCollection.STORAGES -> List.of(authHandler, new IntHandlerForStorages(hub)); + case NakshaAdminCollection.EXTENSIONS -> List.of(authHandler, new IntHandlerForExtensions(hub)); + default -> throw unchecked(new Exception("Unsupported virtual space " + spaceId)); + }); + } + return adminSpaces; + } +} diff --git a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/NakshaHubConfiguration.java b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/NakshaHubConfiguration.java new file mode 100644 index 000000000..3dee574ed --- /dev/null +++ b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/NakshaHubConfiguration.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017-2023 HERE Europe B.V. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ +package com.here.naksha.lib.hub2; + +import com.here.naksha.lib.core.NakshaContext; +import com.here.naksha.lib.hub2.admin.AdminStorage; +import com.here.naksha.lib.hub2.admin.AdminStorageConfiguration; +import com.here.naksha.lib.hub2.space.SpaceStorage; +import com.here.naksha.lib.hub2.space.SpaceStorageConfiguration; +import com.here.naksha.lib.psql.PsqlConfig; + +public class NakshaHubConfiguration { + private NakshaHubConfiguration() {} + + public static NakshaHub2 nakshaHub(PsqlConfig psqlConfig, NakshaContext nakshaContext) { + AdminStorage adminStorage = AdminStorageConfiguration.psqlAdminStorage(psqlConfig, nakshaContext); + SpaceStorage spaceStorage = SpaceStorageConfiguration.adminBasedSpaceStorage(adminStorage); + return new NakshaHub2(adminStorage, spaceStorage); + } +} diff --git a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/admin/AdminStorage.java b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/admin/AdminStorage.java new file mode 100644 index 000000000..652441bc0 --- /dev/null +++ b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/admin/AdminStorage.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2017-2023 HERE Europe B.V. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ +package com.here.naksha.lib.hub2.admin; + +import static com.here.naksha.lib.core.exceptions.UncheckedException.unchecked; +import static com.here.naksha.lib.core.util.storage.RequestHelper.createFeatureRequest; + +import com.here.naksha.lib.core.NakshaAdminCollection; +import com.here.naksha.lib.core.NakshaContext; +import com.here.naksha.lib.core.models.naksha.Storage; +import com.here.naksha.lib.core.models.storage.EWriteOp; +import com.here.naksha.lib.core.models.storage.ErrorResult; +import com.here.naksha.lib.core.models.storage.Result; +import com.here.naksha.lib.core.models.storage.StorageCollection; +import com.here.naksha.lib.core.models.storage.WriteCollections; +import com.here.naksha.lib.core.models.storage.WriteOp; +import com.here.naksha.lib.core.storage.IStorage; +import com.here.naksha.lib.core.storage.IWriteSession; +import java.util.ArrayList; +import java.util.List; + +/** + * IStorage responsible for Admin operations in Naksha + */ +public abstract class AdminStorage implements IStorage { + + private final IStorage physicalAdminStorage; + + protected AdminStorage(IStorage physicalAdminStorage, NakshaContext nakshaContext) { + this.physicalAdminStorage = physicalAdminStorage; + nakshaContext.attachToCurrentThread(); + createAdminCollections(nakshaContext); + ensureHistoryPartitionsAvailability(); + ensureDefaultStorageImplementationPresent(nakshaContext); + } + + @Override + public void initStorage() { + this.physicalAdminStorage.initStorage(); + } + + @Override + public void startMaintainer() { + this.physicalAdminStorage.startMaintainer(); + } + + @Override + public void maintainNow() { + this.physicalAdminStorage.maintainNow(); + } + + @Override + public void stopMaintainer() { + this.physicalAdminStorage.stopMaintainer(); + } + + protected abstract Storage fetchDefaultStorage(); + + private void createAdminCollections(NakshaContext nakshaContext) { + try (final IWriteSession admin = physicalAdminStorage.newWriteSession(nakshaContext, true)) { + final List> collectionList = new ArrayList<>(); + for (final String name : NakshaAdminCollection.ALL) { + final StorageCollection collection = new StorageCollection(name); + final WriteOp writeOp = new WriteOp<>(EWriteOp.INSERT, collection, false); + collectionList.add(writeOp); + } + final Result wrResult = admin.execute(new WriteCollections<>(collectionList)); + if (wrResult == null) { + admin.rollback(); + throw unchecked(new Exception("Unable to create Admin collections in Admin DB. Null result!")); + } else if (wrResult instanceof ErrorResult er) { + admin.rollback(); + throw unchecked(new Exception( + "Unable to create Admin collections in Admin DB. " + er.toString(), er.exception)); + } + admin.commit(); + } + } + + private void ensureHistoryPartitionsAvailability() { + physicalAdminStorage.maintainNow(); + } + + private void ensureDefaultStorageImplementationPresent(NakshaContext nakshaContext) { + final Storage defaultStorage = fetchDefaultStorage(); + try (final IWriteSession admin = physicalAdminStorage.newWriteSession(nakshaContext, true)) { + // persist in Admin DB (if not already exists) + final Result writeDefaultStorageResult = + admin.execute(createFeatureRequest(NakshaAdminCollection.STORAGES, defaultStorage, true)); + if (writeDefaultStorageResult == null) { + admin.rollback(); + throw unchecked(new Exception("Unable to add default storage in Admin DB. Null result!")); + } else if (writeDefaultStorageResult instanceof ErrorResult er) { + admin.rollback(); + throw unchecked( + new Exception("Unable to add default storage in Admin DB. " + er.toString(), er.exception)); + } + admin.commit(); + } + } +} diff --git a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/admin/AdminStorageConfiguration.java b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/admin/AdminStorageConfiguration.java new file mode 100644 index 000000000..ac3a5d2dd --- /dev/null +++ b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/admin/AdminStorageConfiguration.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017-2023 HERE Europe B.V. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ +package com.here.naksha.lib.hub2.admin; + +import com.here.naksha.lib.core.NakshaContext; +import com.here.naksha.lib.psql.PsqlConfig; + +public class AdminStorageConfiguration { + private AdminStorageConfiguration() {} + + public static AdminStorage psqlAdminStorage(PsqlConfig psqlConfig, NakshaContext nakshaContext) { + return new PsqlAdminStorage(psqlConfig, nakshaContext); + } +} diff --git a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/admin/PsqlAdminStorage.java b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/admin/PsqlAdminStorage.java new file mode 100644 index 000000000..919374bec --- /dev/null +++ b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/admin/PsqlAdminStorage.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017-2023 HERE Europe B.V. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ +package com.here.naksha.lib.hub2.admin; + +import static com.here.naksha.lib.core.exceptions.UncheckedException.unchecked; + +import com.here.naksha.lib.core.NakshaContext; +import com.here.naksha.lib.core.models.naksha.Storage; +import com.here.naksha.lib.core.util.IoHelp; +import com.here.naksha.lib.core.util.json.Json; +import com.here.naksha.lib.core.view.ViewDeserialize; +import com.here.naksha.lib.psql.PsqlConfig; +import com.here.naksha.lib.psql.PsqlStorage; +import org.jetbrains.annotations.NotNull; + +final class PsqlAdminStorage extends AdminStorage { + + private static final String DEFAULT_STORAGE_CONFIG_PATH = "config/default-storage.json"; + private static final String STORAGE_ID = "naksha-admin-db"; + + PsqlAdminStorage(@NotNull PsqlConfig psqlConfig, @NotNull NakshaContext nakshaContext) { + super(new PsqlStorage(psqlConfig, STORAGE_ID), nakshaContext); + } + + @Override + protected Storage fetchDefaultStorage() { + try (final Json json = Json.get()) { + final String storageJson = IoHelp.readResource(DEFAULT_STORAGE_CONFIG_PATH); + return json.reader(ViewDeserialize.Storage.class) + .forType(Storage.class) + .readValue(storageJson); + } catch (Exception e) { + throw unchecked(new Exception("Unable to read default Storage file. " + e.getMessage(), e)); + } + } +} diff --git a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/space/AdminBasedSpaceStorage.java b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/space/AdminBasedSpaceStorage.java new file mode 100644 index 000000000..aed36a4ea --- /dev/null +++ b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/space/AdminBasedSpaceStorage.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2017-2023 HERE Europe B.V. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ +package com.here.naksha.lib.hub2.space; + +import com.here.naksha.lib.core.NakshaContext; +import com.here.naksha.lib.core.NakshaVersion; +import com.here.naksha.lib.core.storage.IReadSession; +import com.here.naksha.lib.core.storage.IWriteSession; +import com.here.naksha.lib.hub2.admin.AdminStorage; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +final class AdminBasedSpaceStorage extends SpaceStorage { + + /** + * List of Admin virtual spaces with relevant event handlers required to support event processing + */ + private final @NotNull AdminStorage adminStorage; + + AdminBasedSpaceStorage(final @NotNull AdminStorage adminStorage) { + super(); + this.adminStorage = adminStorage; + } + + /** + * Initializes the storage, create the transaction table, install needed scripts and extensions. + */ + @Override + @ApiStatus.AvailableSince(NakshaVersion.v2_0_7) + public void initStorage() { + adminStorage.initStorage(); + } + + /** + * Starts the maintainer thread that will take about history garbage collection, sequencing and other background jobs. + */ + @Override + @ApiStatus.AvailableSince(NakshaVersion.v2_0_7) + public void startMaintainer() { + adminStorage.startMaintainer(); + } + + /** + * Blocking call to perform maintenance tasks right now. One-time maintenance. + */ + @Override + @ApiStatus.AvailableSince(NakshaVersion.v2_0_7) + public void maintainNow() { + adminStorage.maintainNow(); + } + + /** + * Stops the maintainer thread. + */ + @Override + @ApiStatus.AvailableSince(NakshaVersion.v2_0_7) + public void stopMaintainer() { + adminStorage.stopMaintainer(); + } + + @Override + @ApiStatus.AvailableSince(NakshaVersion.v2_0_7) + public @NotNull IWriteSession newWriteSession(@Nullable NakshaContext context, boolean useMaster) { + if (virtualSpaces == null) { + throw new IllegalStateException("Unable to create new write session: virtual spaces were not initialized"); + } + if (eventPipelineFactory == null) { + throw new IllegalStateException( + "Unable to create new write session: event pipeline factory was not initialized"); + } + return new AdminBasedSpaceStorageWriter(eventPipelineFactory, adminStorage, virtualSpaces, context, useMaster); + } + + @Override + @ApiStatus.AvailableSince(NakshaVersion.v2_0_7) + public @NotNull IReadSession newReadSession(@Nullable NakshaContext context, boolean useMaster) { + if (virtualSpaces == null) { + throw new IllegalStateException("Unable to create new write session: virtual spaces were not initialized"); + } + if (eventPipelineFactory == null) { + throw new IllegalStateException( + "Unable to create new write session: event pipeline factory was not initialized"); + } + return new AdminBasedSpaceStorageReader(eventPipelineFactory, adminStorage, virtualSpaces, context, useMaster); + } +} diff --git a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/space/AdminBasedSpaceStorageReader.java b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/space/AdminBasedSpaceStorageReader.java new file mode 100644 index 000000000..01aa14378 --- /dev/null +++ b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/space/AdminBasedSpaceStorageReader.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2017-2023 HERE Europe B.V. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ +package com.here.naksha.lib.hub2.space; + +import com.here.naksha.lib.core.EventPipeline; +import com.here.naksha.lib.core.IEventHandler; +import com.here.naksha.lib.core.NakshaContext; +import com.here.naksha.lib.core.NakshaVersion; +import com.here.naksha.lib.core.models.storage.Notification; +import com.here.naksha.lib.core.models.storage.ReadCollections; +import com.here.naksha.lib.core.models.storage.ReadFeatures; +import com.here.naksha.lib.core.models.storage.ReadRequest; +import com.here.naksha.lib.core.models.storage.Result; +import com.here.naksha.lib.core.storage.IReadSession; +import com.here.naksha.lib.hub2.EventPipelineFactory; +import com.here.naksha.lib.hub2.admin.AdminStorage; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +class AdminBasedSpaceStorageReader implements IReadSession { + + final EventPipelineFactory eventPipelineFactory; + + final AdminStorage adminStorage; + + /** + * Runtime NakshaContext which is to be associated with read operations + */ + final @NotNull NakshaContext context; + + /** + * Flag to indicate whether it has to connect to master storage instance or not + */ + final boolean useMaster; + + /** + * List of Admin virtual spaces with relevant event handlers required to support event processing + */ + final @NotNull Map> virtualSpaces; + + @ApiStatus.AvailableSince(NakshaVersion.v2_0_7) + public AdminBasedSpaceStorageReader( + final @NotNull EventPipelineFactory eventPipelineFactory, + final @NotNull AdminStorage adminStorage, + final @NotNull Map> virtualSpaces, + final @Nullable NakshaContext context, + boolean useMaster) { + this.eventPipelineFactory = eventPipelineFactory; + this.adminStorage = adminStorage; + this.context = (context != null) ? context : NakshaContext.currentContext(); + this.useMaster = useMaster; + this.virtualSpaces = virtualSpaces; + } + + /** + * Tests whether this session is connected to the master-node. + * + * @return {@code true}, if this session is connected to the master-node; {@code false} otherwise. + */ + @Override + @ApiStatus.AvailableSince(NakshaVersion.v2_0_7) + public boolean isMasterConnect() { + return useMaster; + } + + /** + * Returns the Naksha context bound to this read-connection. + * + * @return the Naksha context bound to this read-connection. + */ + @Override + @ApiStatus.AvailableSince(NakshaVersion.v2_0_7) + public @NotNull NakshaContext getNakshaContext() { + return this.context; + } + + /** + * Returns the statement timeout. + * + * @param timeUnit The time-unit in which to return the timeout. + * @return The timeout. + */ + @Override + @ApiStatus.AvailableSince(NakshaVersion.v2_0_7) + public long getStatementTimeout(@NotNull TimeUnit timeUnit) { + return 0; + } + + /** + * Sets the statement timeout. + * + * @param timeout The timeout to set. + * @param timeUnit The unit of the timeout. + */ + @Override + @ApiStatus.AvailableSince(NakshaVersion.v2_0_7) + public void setStatementTimeout(long timeout, @NotNull TimeUnit timeUnit) {} + + /** + * Returns the lock timeout. + * + * @param timeUnit The time-unit in which to return the timeout. + * @return The timeout. + */ + @Override + @ApiStatus.AvailableSince(NakshaVersion.v2_0_7) + public long getLockTimeout(@NotNull TimeUnit timeUnit) { + return 0; + } + + /** + * Sets the lock timeout. + * + * @param timeout The timeout to set. + * @param timeUnit The unit of the timeout. + */ + @Override + @ApiStatus.AvailableSince(NakshaVersion.v2_0_7) + public void setLockTimeout(long timeout, @NotNull TimeUnit timeUnit) {} + + /** + * Execute the given read-request. + * + * @param readRequest input read request + * @return the result. + */ + @Override + @ApiStatus.AvailableSince(NakshaVersion.v2_0_7) + public @NotNull Result execute(final @NotNull ReadRequest readRequest) { + if (readRequest instanceof ReadCollections rc) { + return executeReadCollections(rc); + } else if (readRequest instanceof ReadFeatures rf) { + return executeReadFeatures(rf); + } + throw new UnsupportedOperationException( + "ReadRequest with unsupported type " + readRequest.getClass().getName()); + } + + private @NotNull Result executeReadCollections(final @NotNull ReadCollections rc) { + try (final IReadSession admin = adminStorage.newReadSession(context, useMaster)) { + return admin.execute(rc); + } + } + + private @NotNull Result executeReadFeatures(final @NotNull ReadFeatures rf) { + if (rf.getCollections().size() > 1) { + throw new UnsupportedOperationException("Reading from multiple spaces not supported!"); + } + if (virtualSpaces.containsKey(rf.getCollections().get(0))) { + // Request is to read from Naksha Admin space + return executeReadFeaturesFromAdminSpaces(rf); + } else { + // Request is to read from Custom space + return executeReadFeaturesFromCustomSpaces(rf); + } + } + + private @NotNull Result executeReadFeaturesFromAdminSpaces(final @NotNull ReadFeatures rf) { + // Run pipeline against virtual space + final EventPipeline pipeline = eventPipelineFactory.eventPipeline(); + // add internal Admin resource specific event handlers + for (final IEventHandler handler : virtualSpaces.get(rf.getCollections().get(0))) { + pipeline.addEventHandler(handler); + } + return pipeline.sendEvent(rf); + } + + private @NotNull Result executeReadFeaturesFromCustomSpaces(final @NotNull ReadFeatures rf) { + // TODO : Add logic to support running pipeline for custom space + throw new UnsupportedOperationException("ReadFeatures from custom space not supported as of now"); + } + + /** + * Process the given notification. + * + * @param notification notification event + * @return the result. + */ + @Override + @ApiStatus.AvailableSince(NakshaVersion.v2_0_7) + public @NotNull Result process(@NotNull Notification notification) { + throw new UnsupportedOperationException("Notification processing not supported!"); + } + + /** + * Closes the session, returns the underlying connection back to the connection pool. Any method of the session will from now on throw an + * {@link IllegalStateException}. + */ + @Override + @ApiStatus.AvailableSince(NakshaVersion.v2_0_7) + public void close() {} +} diff --git a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/space/AdminBasedSpaceStorageWriter.java b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/space/AdminBasedSpaceStorageWriter.java new file mode 100644 index 000000000..6231f46ce --- /dev/null +++ b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/space/AdminBasedSpaceStorageWriter.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2017-2023 HERE Europe B.V. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ +package com.here.naksha.lib.hub2.space; + +import com.here.naksha.lib.core.EventPipeline; +import com.here.naksha.lib.core.IEventHandler; +import com.here.naksha.lib.core.NakshaContext; +import com.here.naksha.lib.core.NakshaVersion; +import com.here.naksha.lib.core.exceptions.StorageLockException; +import com.here.naksha.lib.core.models.storage.Result; +import com.here.naksha.lib.core.models.storage.WriteCollections; +import com.here.naksha.lib.core.models.storage.WriteFeatures; +import com.here.naksha.lib.core.models.storage.WriteRequest; +import com.here.naksha.lib.core.storage.IStorageLock; +import com.here.naksha.lib.core.storage.IWriteSession; +import com.here.naksha.lib.hub2.EventPipelineFactory; +import com.here.naksha.lib.hub2.admin.AdminStorage; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +final class AdminBasedSpaceStorageWriter extends AdminBasedSpaceStorageReader implements IWriteSession { + + AdminBasedSpaceStorageWriter( + final @NotNull EventPipelineFactory eventPipelineFactory, + final @NotNull AdminStorage adminStorage, + final @NotNull Map> virtualSpaces, + final @Nullable NakshaContext context, + boolean useMaster) { + super(eventPipelineFactory, adminStorage, virtualSpaces, context, useMaster); + } + + /** + * Execute the given write-request. + * + * @param writeRequest the write-request to execute. + * @return the result. + */ + @Override + @ApiStatus.AvailableSince(NakshaVersion.v2_0_7) + public @NotNull Result execute(@NotNull WriteRequest writeRequest) { + if (writeRequest instanceof WriteCollections wc) { + return executeWriteCollections(wc); + } else if (writeRequest instanceof WriteFeatures wf) { + return executeWriteFeatures(wf); + } + throw new UnsupportedOperationException( + "WriteRequest with unsupported type " + writeRequest.getClass().getName()); + } + + private @NotNull Result executeWriteCollections(final @NotNull WriteCollections wc) { + try (final IWriteSession admin = adminStorage.newWriteSession(context, useMaster)) { + return admin.execute(wc); + } + } + + private @NotNull Result executeWriteFeatures(final @NotNull WriteFeatures wf) { + if (virtualSpaces.containsKey(wf.collectionId)) { + // Request is to write to Naksha Admin space + return executeWriteFeaturesToAdminSpaces(wf); + } else { + // Request is to write to Custom space + return executeWriteFeaturesToCustomSpaces(wf); + } + } + + private @NotNull Result executeWriteFeaturesToAdminSpaces(final @NotNull WriteFeatures wf) { + // Run pipeline against virtual space + final EventPipeline pipeline = eventPipelineFactory.eventPipeline(); + // add internal Admin resource specific event handlers + for (final IEventHandler handler : virtualSpaces.get(wf.collectionId)) { + pipeline.addEventHandler(handler); + } + return pipeline.sendEvent(wf); + } + + private @NotNull Result executeWriteFeaturesToCustomSpaces(final @NotNull WriteFeatures rf) { + // TODO : Add logic to support running pipeline for custom space + throw new UnsupportedOperationException("WriteFeatures to custom space not supported as of now"); + } + + /** + * Acquire a lock to a specific feature in the HEAD state. + * + * @param collectionId the collection in which the feature is stored. + * @param featureId the identifier of the feature to lock. + * @param timeout the maximum time to wait for the lock. + * @param timeUnit the time-unit in which the wait-time was provided. + * @return the lock. + * @throws StorageLockException if the locking failed. + */ + @Override + @ApiStatus.AvailableSince(NakshaVersion.v2_0_7) + public @NotNull IStorageLock lockFeature( + @NotNull String collectionId, @NotNull String featureId, long timeout, @NotNull TimeUnit timeUnit) + throws StorageLockException { + throw new UnsupportedOperationException("Locking not supported by this storage instance!"); + } + + /** + * Acquire an advisory lock. + * + * @param lockId the unique identifier of the lock to acquire. + * @param timeout the maximum time to wait for the lock. + * @param timeUnit the time-unit in which the wait-time was provided. + * @return the lock. + * @throws StorageLockException if the locking failed. + */ + @Override + @ApiStatus.AvailableSince(NakshaVersion.v2_0_7) + public @NotNull IStorageLock lockStorage(@NotNull String lockId, long timeout, @NotNull TimeUnit timeUnit) + throws StorageLockException { + throw new UnsupportedOperationException("Locking not supported by this storage instance!"); + } + + /** + * Commit all changes. + */ + @Override + @ApiStatus.AvailableSince(NakshaVersion.v2_0_7) + public void commit() {} + + /** + * Abort the transaction, revert all pending changes. + */ + @Override + @ApiStatus.AvailableSince(NakshaVersion.v2_0_7) + public void rollback() {} +} diff --git a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/space/SpaceStorage.java b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/space/SpaceStorage.java new file mode 100644 index 000000000..44e48a92e --- /dev/null +++ b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/space/SpaceStorage.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017-2023 HERE Europe B.V. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ +package com.here.naksha.lib.hub2.space; + +import com.here.naksha.lib.core.IEventHandler; +import com.here.naksha.lib.core.storage.IStorage; +import com.here.naksha.lib.hub2.EventPipelineFactory; +import java.util.List; +import java.util.Map; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public abstract class SpaceStorage implements IStorage { + + protected @Nullable Map> virtualSpaces; + + protected @Nullable EventPipelineFactory eventPipelineFactory; + + public void setVirtualSpaces(@NotNull Map> virtualSpaces) { + this.virtualSpaces = virtualSpaces; + } + + public void setEventPipelineFactory(@NotNull EventPipelineFactory eventPipelineFactory) { + this.eventPipelineFactory = eventPipelineFactory; + } +} diff --git a/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/space/SpaceStorageConfiguration.java b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/space/SpaceStorageConfiguration.java new file mode 100644 index 000000000..7121f4c31 --- /dev/null +++ b/here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub2/space/SpaceStorageConfiguration.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2017-2023 HERE Europe B.V. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ +package com.here.naksha.lib.hub2.space; + +import com.here.naksha.lib.hub2.admin.AdminStorage; + +public class SpaceStorageConfiguration { + private SpaceStorageConfiguration() {} + + public static SpaceStorage adminBasedSpaceStorage(AdminStorage adminStorage) { + return new AdminBasedSpaceStorage(adminStorage); + } +} diff --git a/here-naksha-lib-hub/src/test/java/com/here/naksha/lib/common/FeatureReaderUtil.java b/here-naksha-lib-hub/src/test/java/com/here/naksha/lib/common/FeatureReaderUtil.java new file mode 100644 index 000000000..48105f1a9 --- /dev/null +++ b/here-naksha-lib-hub/src/test/java/com/here/naksha/lib/common/FeatureReaderUtil.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017-2023 HERE Europe B.V. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ +package com.here.naksha.lib.common; + +import static org.junit.jupiter.api.Assertions.fail; + +import com.here.naksha.lib.core.INaksha; +import com.here.naksha.lib.core.NakshaContext; +import com.here.naksha.lib.core.models.storage.ErrorResult; +import com.here.naksha.lib.core.models.storage.ReadFeatures; +import com.here.naksha.lib.core.models.storage.ReadResult; +import com.here.naksha.lib.core.models.storage.Result; +import com.here.naksha.lib.core.storage.IReadSession; +import java.util.Iterator; + +public class FeatureReaderUtil { + + private FeatureReaderUtil() {} + + public static T fetchSingleFeatureFromSpace( + INaksha hub, NakshaContext nakshaContext, ReadFeatures readFeaturesRequest, Class featureType) { + try (final IReadSession reader = hub.getSpaceStorage().newReadSession(nakshaContext, false)) { + final Result result = reader.execute(readFeaturesRequest); + if (result == null) { + fail("Storage read result is null!"); + } else if (result instanceof ErrorResult er) { + fail("Exception reading storages " + er); + } else if (result instanceof ReadResult rr) { + Iterator features = rr.withFeatureType(featureType).iterator(); + return features.next(); + } else { + fail("Unexpected result while reading storages : " + result.getClass()); + } + } + throw new RuntimeException("Improve this line"); // TODO: <-- what he says + } +} diff --git a/here-naksha-lib-hub/src/test/java/com/here/naksha/lib/common/SampleNakshaContext.java b/here-naksha-lib-hub/src/test/java/com/here/naksha/lib/common/SampleNakshaContext.java new file mode 100644 index 000000000..e9265f994 --- /dev/null +++ b/here-naksha-lib-hub/src/test/java/com/here/naksha/lib/common/SampleNakshaContext.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2017-2023 HERE Europe B.V. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ +package com.here.naksha.lib.common; + +import com.here.naksha.lib.core.NakshaContext; +import com.here.naksha.lib.hub.NakshaHubConfig; + +public class SampleNakshaContext { + + private SampleNakshaContext() {} + + public static final NakshaContext NAKSHA_CONTEXT = new NakshaContext().withAppId(NakshaHubConfig.defaultAppName()); +} diff --git a/here-naksha-lib-hub/src/test/java/com/here/naksha/lib/common/TestFileLoader.java b/here-naksha-lib-hub/src/test/java/com/here/naksha/lib/common/TestFileLoader.java new file mode 100644 index 000000000..cd0e10341 --- /dev/null +++ b/here-naksha-lib-hub/src/test/java/com/here/naksha/lib/common/TestFileLoader.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017-2023 HERE Europe B.V. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ +package com.here.naksha.lib.common; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import org.junit.jupiter.api.Assertions; + +public class TestFileLoader { + + private static final String TEST_DATA_FOLDER = "src/test/resources/unit_test_data/"; + + private TestFileLoader() {} + + public static String loadFileOrFail(String fileName) { + try { + return new String(Files.readAllBytes(Paths.get(TEST_DATA_FOLDER + fileName))); + } catch (IOException e) { + Assertions.fail("Unable tor read test file " + fileName, e); + return null; + } + } +} diff --git a/here-naksha-lib-hub/src/test/java/com/here/naksha/lib/hub2/GetFeaturesTest.java b/here-naksha-lib-hub/src/test/java/com/here/naksha/lib/hub2/GetFeaturesTest.java new file mode 100644 index 000000000..ec135a941 --- /dev/null +++ b/here-naksha-lib-hub/src/test/java/com/here/naksha/lib/hub2/GetFeaturesTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2017-2023 HERE Europe B.V. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ +package com.here.naksha.lib.hub2; + +import static com.here.naksha.lib.common.FeatureReaderUtil.fetchSingleFeatureFromSpace; +import static com.here.naksha.lib.common.SampleNakshaContext.NAKSHA_CONTEXT; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import com.here.naksha.lib.common.TestFileLoader; +import com.here.naksha.lib.core.NakshaAdminCollection; +import com.here.naksha.lib.core.models.storage.IAdvancedReadResult; +import com.here.naksha.lib.core.models.storage.ReadFeatures; +import com.here.naksha.lib.core.models.storage.ReadResult; +import com.here.naksha.lib.core.storage.IReadSession; +import com.here.naksha.lib.hub2.admin.AdminStorage; +import com.here.naksha.lib.hub2.space.SpaceStorage; +import java.util.Iterator; +import java.util.List; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +public class GetFeaturesTest { + + NakshaHub2 hub; + + @Mock + AdminStorage adminStorage; + + @Mock + SpaceStorage spaceStorage; + + @BeforeEach + void setup() { + MockitoAnnotations.openMocks(this); + hub = new NakshaHub2(adminStorage, spaceStorage); + } + + @Test + void shouldReturnFeatures() { + // Given: expected result + String expectedStorages = TestFileLoader.loadFileOrFail("TC0001_getStorages/body_part.json"); + + // And: Sample request + ReadFeatures readFeaturesRequest = new ReadFeatures(NakshaAdminCollection.STORAGES); + + // And: space storage returning expected result + IReadSession readSession = Mockito.mock(IReadSession.class); + when(spaceStorage.newReadSession(eq(NAKSHA_CONTEXT), anyBoolean())).thenReturn(readSession); + when(readSession.execute(readFeaturesRequest)).thenReturn(new SimpleStringResult(expectedStorages)); + + // When: fetching feature from space storage via hub + String fetchedFeature = fetchSingleFeatureFromSpace(hub, NAKSHA_CONTEXT, readFeaturesRequest, String.class); + + // Then: expected result matches fetched feature + assertEquals(expectedStorages, fetchedFeature); + } + + static class SimpleStringResult extends ReadResult { + + private final String value; + + public SimpleStringResult(String value) { + super(String.class); + this.value = value; + } + + @Override + public IAdvancedReadResult advanced() { + return null; + } + + @Override + protected void onFeatureTypeChange() { + // not needed in test impl + } + + @NotNull + @Override + public Iterator iterator() { + return List.of(value).iterator(); + } + } +} diff --git a/here-naksha-lib-hub/src/test/java/com/here/naksha/lib/hub2/NakshaHub2Test.java b/here-naksha-lib-hub/src/test/java/com/here/naksha/lib/hub2/NakshaHub2Test.java new file mode 100644 index 000000000..cec4affa1 --- /dev/null +++ b/here-naksha-lib-hub/src/test/java/com/here/naksha/lib/hub2/NakshaHub2Test.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2017-2023 HERE Europe B.V. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ +package com.here.naksha.lib.hub2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import com.here.naksha.lib.core.IEventHandler; +import com.here.naksha.lib.core.NakshaAdminCollection; +import com.here.naksha.lib.hub2.admin.AdminStorage; +import com.here.naksha.lib.hub2.space.SpaceStorage; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +class NakshaHub2Test { + + @Mock + private AdminStorage adminStorage; + + @Mock + private SpaceStorage spaceStorage; + + @BeforeEach + void setup() { + MockitoAnnotations.openMocks(this); + } + + @Test + void shouldReturnSuppliedStorages() { + // Given: Naksha Hub instance with admin and space storages supplied + NakshaHub2 hub = new NakshaHub2(adminStorage, spaceStorage); + + // Then: Corresponding Admin Storage is retrievable + assertEquals(hub.getAdminStorage(), adminStorage); + + // And: Corresponding Space Storage is retrievable + assertEquals(hub.getSpaceStorage(), spaceStorage); + } + + @Test + void shouldConfigureVirtualSpacesForSpaceStorage() { + // When: Instantiating new Naksha Hub instance + NakshaHub2 hub = new NakshaHub2(adminStorage, spaceStorage); + + // Then: Space storage should be configured with 'some' virtual spaces + ArgumentCaptor>> virtualSpaceCaptor = ArgumentCaptor.forClass(Map.class); + verify(spaceStorage, times(1)).setVirtualSpaces(virtualSpaceCaptor.capture()); + + // And: Virtual spaces passed to space storage should include all admin spaces + Set expectedAdminSpaceIds = new HashSet<>(NakshaAdminCollection.ALL); + Set passedSpaceIds = virtualSpaceCaptor.getValue().keySet(); + assertEquals(expectedAdminSpaceIds, passedSpaceIds); + } + + @Test + void shouldConfigureEventPipelineFactoryForSpaceStorage() { + // When: Instantiating new Naksha Hub instance + NakshaHub2 hub = new NakshaHub2(adminStorage, spaceStorage); + + // Then: Space storage should be configured with new virtual spaces + verify(spaceStorage, times(1)).setEventPipelineFactory(new NakshaEventPipelineFactory(hub)); + } +} diff --git a/here-naksha-lib-hub/src/test/java/com/here/naksha/lib/hub2/admin/AdminStorageTest.java b/here-naksha-lib-hub/src/test/java/com/here/naksha/lib/hub2/admin/AdminStorageTest.java new file mode 100644 index 000000000..f43219eb8 --- /dev/null +++ b/here-naksha-lib-hub/src/test/java/com/here/naksha/lib/hub2/admin/AdminStorageTest.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2017-2023 HERE Europe B.V. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ +package com.here.naksha.lib.hub2.admin; + +import static com.here.naksha.lib.common.SampleNakshaContext.NAKSHA_CONTEXT; +import static com.here.naksha.lib.core.models.storage.EWriteOp.INSERT; +import static com.here.naksha.lib.hub2.admin.AdminStorageTest.TestAdminStorage.DEFAULT_STORAGE_TEST_CLASS_NAME; +import static com.here.naksha.lib.hub2.admin.AdminStorageTest.TestAdminStorage.DEFAULT_STORAGE_TEST_ID; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.here.naksha.lib.core.NakshaAdminCollection; +import com.here.naksha.lib.core.NakshaContext; +import com.here.naksha.lib.core.models.naksha.Storage; +import com.here.naksha.lib.core.models.storage.StorageCollection; +import com.here.naksha.lib.core.models.storage.SuccessResult; +import com.here.naksha.lib.core.models.storage.WriteCollections; +import com.here.naksha.lib.core.models.storage.WriteFeatures; +import com.here.naksha.lib.core.models.storage.WriteOp; +import com.here.naksha.lib.core.models.storage.WriteRequest; +import com.here.naksha.lib.core.storage.IStorage; +import com.here.naksha.lib.core.storage.IWriteSession; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +class AdminStorageTest { + + @Mock + IStorage physicalAdminStorage; + + @BeforeEach + void setup() { + MockitoAnnotations.openMocks(this); + } + + @Test + void shouldCreateAdminCollections() { + // Given: Physical admin storage that's capable of creating write sessions + IWriteSession physicalWriteSession = mock(IWriteSession.class); + when(physicalAdminStorage.newWriteSession(NAKSHA_CONTEXT, true)).thenReturn(physicalWriteSession); + + // And: all write requests to physical storage are successful + when(physicalWriteSession.execute(any(WriteRequest.class))).thenReturn(new SuccessResult()); + + // When: creating new Admin Storage + new TestAdminStorage(physicalAdminStorage, NAKSHA_CONTEXT); + + // Then: There was a write request on physical admin storage + ArgumentCaptor adminWriteRequests = ArgumentCaptor.forClass(WriteRequest.class); + verify(physicalWriteSession, times(2)).execute(adminWriteRequests.capture()); + + // And: First write requests was about writing collections + WriteRequest firstWriteRequest = adminWriteRequests.getAllValues().get(0); + Assertions.assertInstanceOf(WriteCollections.class, firstWriteRequest); + List writeOps = ((WriteCollections) firstWriteRequest).queries; + + // And: All writing collection operations were INSERT for Admin Collections + Assertions.assertTrue(writeOps.stream().map(writeOp -> writeOp.op).allMatch(op -> op == INSERT)); + Set insertedStoragesIds = writeOps.stream() + .map(writeOp -> ((StorageCollection) writeOp.feature).getId()) + .collect(Collectors.toSet()); + Assertions.assertEquals(new HashSet<>(NakshaAdminCollection.ALL), insertedStoragesIds); + } + + @Test + void shouldRunMaintenanceToEnsureParitionsAvailability() { + // Given: Physical admin storage that's capable of creating write sessions + IWriteSession physicalWriteSession = mock(IWriteSession.class); + when(physicalAdminStorage.newWriteSession(NAKSHA_CONTEXT, true)).thenReturn(physicalWriteSession); + + // And: all write requests to physical storage are successful + when(physicalWriteSession.execute(any(WriteRequest.class))).thenReturn(new SuccessResult()); + + // When: creating new Admin Storage + new TestAdminStorage(physicalAdminStorage, NAKSHA_CONTEXT); + + // Then; maintenance is run + verify(physicalAdminStorage, times(1)).maintainNow(); + } + + @Test + void shouldInsertDefaultStorage() { + // Given: Physical admin storage that's capable of creating write sessions + IWriteSession physicalWriteSession = mock(IWriteSession.class); + when(physicalAdminStorage.newWriteSession(NAKSHA_CONTEXT, true)).thenReturn(physicalWriteSession); + + // And: all write requests to physical storage are successful + when(physicalWriteSession.execute(any(WriteRequest.class))).thenReturn(new SuccessResult()); + + // When: creating new Admin Storage + AdminStorage adminStorage = new TestAdminStorage(physicalAdminStorage, NAKSHA_CONTEXT); + + // Then: There was a write request on physical admin storage + ArgumentCaptor adminWriteRequests = ArgumentCaptor.forClass(WriteRequest.class); + verify(physicalWriteSession, times(2)).execute(adminWriteRequests.capture()); + + // And: First write requests was about writing features + WriteRequest firstWriteRequest = adminWriteRequests.getAllValues().get(1); + Assertions.assertInstanceOf(WriteFeatures.class, firstWriteRequest); + List writeOps = ((WriteFeatures) firstWriteRequest).queries; + + // And: There was one operation and it was about wirting admin storage + Assertions.assertEquals(1, writeOps.size()); + WriteOp writeOp = writeOps.get(0); + Assertions.assertInstanceOf(Storage.class, writeOp.feature); + Storage insertedStorage = (Storage) writeOp.feature; + Assertions.assertEquals(DEFAULT_STORAGE_TEST_CLASS_NAME, insertedStorage.getClassName()); + Assertions.assertEquals(DEFAULT_STORAGE_TEST_ID, insertedStorage.getId()); + } + + static class TestAdminStorage extends AdminStorage { + + static final String DEFAULT_STORAGE_TEST_CLASS_NAME = "TestAdminStorage"; + static final String DEFAULT_STORAGE_TEST_ID = "test_admin_storage_id"; + + protected TestAdminStorage(IStorage physicalAdminStorage, NakshaContext nakshaContext) { + super(physicalAdminStorage, nakshaContext); + } + + @Override + protected Storage fetchDefaultStorage() { + return new Storage(DEFAULT_STORAGE_TEST_CLASS_NAME, DEFAULT_STORAGE_TEST_ID); + } + } +} diff --git a/here-naksha-lib-hub/src/test/java/com/here/naksha/lib/hub2/space/AdminBasedSpaceStorageTest.java b/here-naksha-lib-hub/src/test/java/com/here/naksha/lib/hub2/space/AdminBasedSpaceStorageTest.java new file mode 100644 index 000000000..cf09e0c64 --- /dev/null +++ b/here-naksha-lib-hub/src/test/java/com/here/naksha/lib/hub2/space/AdminBasedSpaceStorageTest.java @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2017-2023 HERE Europe B.V. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ +package com.here.naksha.lib.hub2.space; + +import static com.here.naksha.lib.common.SampleNakshaContext.NAKSHA_CONTEXT; +import static java.util.Collections.emptyList; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.here.naksha.lib.core.EventPipeline; +import com.here.naksha.lib.core.IEventHandler; +import com.here.naksha.lib.core.models.storage.Notification; +import com.here.naksha.lib.core.models.storage.ReadCollections; +import com.here.naksha.lib.core.models.storage.ReadFeatures; +import com.here.naksha.lib.core.models.storage.ReadRequest; +import com.here.naksha.lib.core.models.storage.WriteCollections; +import com.here.naksha.lib.core.models.storage.WriteFeatures; +import com.here.naksha.lib.core.models.storage.WriteRequest; +import com.here.naksha.lib.core.storage.IReadSession; +import com.here.naksha.lib.core.storage.IWriteSession; +import com.here.naksha.lib.hub2.EventPipelineFactory; +import com.here.naksha.lib.hub2.admin.AdminStorage; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +class AdminBasedSpaceStorageTest { + + private static final String ADMIN_SPACE_ID = "test_admin_space"; + + @Mock + AdminStorage adminStorage; + + @Mock + EventPipelineFactory eventPipelineFactory; + + @Mock + IEventHandler eventHandler; + + AdminBasedSpaceStorage spaceStorage; + + @BeforeEach + void setup() { + MockitoAnnotations.openMocks(this); + spaceStorage = new AdminBasedSpaceStorage(adminStorage); + spaceStorage.setVirtualSpaces(Map.of(ADMIN_SPACE_ID, List.of(eventHandler))); + spaceStorage.setEventPipelineFactory(eventPipelineFactory); + } + + @Nested + class ReadingTest { + + @Test + void shouldFailWhenCreatingReadSessionWithoutVirtualSpaces() { + // When: virtual spaces were not initialized + spaceStorage.setVirtualSpaces(null); + + // Then: opening new read session fails + assertThrows(IllegalStateException.class, () -> { + spaceStorage.newReadSession(NAKSHA_CONTEXT, true); + }); + } + + @Test + void shouldFailWhenCreatingReadSessionWithoutEventPipelineFactory() { + // When: virtual spaces were not initialized + spaceStorage.setEventPipelineFactory(null); + + // Then: opening new read session fails + assertThrows(IllegalStateException.class, () -> { + spaceStorage.newReadSession(NAKSHA_CONTEXT, true); + }); + } + + @Test + void shouldFailWhenExecutingUnknownReadRequest() { + // Given: new read session + IReadSession readSession = spaceStorage.newReadSession(NAKSHA_CONTEXT, true); + + // And: read request of unknown type + ReadRequest rr = new TestReadRequest(); + + // Then: executing read request fails + assertThrows(UnsupportedOperationException.class, () -> readSession.execute(rr)); + } + + @Test + void shouldDelegateToAdminWhenReadingCollections() { + // Given: new read session + IReadSession readSession = spaceStorage.newReadSession(NAKSHA_CONTEXT, true); + + // And: admin that is able to create reading session + IReadSession adminReadSession = mock(IReadSession.class); + when(adminStorage.newReadSession(NAKSHA_CONTEXT, true)).thenReturn(adminReadSession); + + // And: read collections request + ReadCollections readCollections = new ReadCollections().withIds("some collection"); + + // When: executin request + readSession.execute(readCollections); + + // Then: admin read session is used to handle read collection request + verify(adminReadSession, times(1)).execute(readCollections); + } + + @Test + void shouldFailWhenReadingFeaturesFromManyCollections() { + // Given: new read session + IReadSession readSession = spaceStorage.newReadSession(NAKSHA_CONTEXT, true); + + // And: read collections request + ReadFeatures readFeaturesFromManyCollections = new ReadFeatures("c1", "c2"); + + // Then: executin request + assertThrows( + UnsupportedOperationException.class, () -> readSession.execute(readFeaturesFromManyCollections)); + } + + @Test + void shouldTriggerEventPipelineWhenReadingFromAdminSpace() { + // Given: new read session + IReadSession readSession = spaceStorage.newReadSession(NAKSHA_CONTEXT, true); + + // And: working event pipeline factory + EventPipeline eventPipeline = mock(EventPipeline.class); + when(eventPipelineFactory.eventPipeline()).thenReturn(eventPipeline); + + // And: read collections request + ReadFeatures readFeaturesFromAdmin = new ReadFeatures(ADMIN_SPACE_ID); + + // When: executing request + readSession.execute(readFeaturesFromAdmin); + + // Then: event pipeline gets triggered to add event handler for admin space + verify(eventPipeline, times(1)).addEventHandler(eventHandler); + } + + @Test + void shouldFailWhenReadingFromCustomSpace() { + // Given: new read session + IReadSession readSession = spaceStorage.newReadSession(NAKSHA_CONTEXT, true); + + // And: read collections request + ReadFeatures readFeaturesFromUnknownSpace = new ReadFeatures("unknown space"); + + // Then: operation fails + assertThrows(UnsupportedOperationException.class, () -> readSession.execute(readFeaturesFromUnknownSpace)); + } + + @Test + void shouldNotSupportProcessingNotifications() { + // Given: new read session + IReadSession readSession = spaceStorage.newReadSession(NAKSHA_CONTEXT, true); + + // Then: processing tets notification fails + assertThrows(UnsupportedOperationException.class, () -> readSession.process(new TestNotification())); + } + } + + @Nested + class WritingTest { + + @Test + void shouldFailWhenCreatingWriteSessionWithoutVirtualSpaces() { + // When: virtual spaces were not initialized + spaceStorage.setVirtualSpaces(null); + + // Then: opening new read session fails + assertThrows(IllegalStateException.class, () -> { + spaceStorage.newWriteSession(NAKSHA_CONTEXT, true); + }); + } + + @Test + void shouldFailWhenCreatingWriteSessionWithoutEventPipelineFactory() { + // When: virtual spaces were not initialized + spaceStorage.setEventPipelineFactory(null); + + // Then: opening new read session fails + assertThrows(IllegalStateException.class, () -> { + spaceStorage.newWriteSession(NAKSHA_CONTEXT, true); + }); + } + + @Test + void shouldFailWhenExecutingUnknownWriteRequest() { + // Given: new write session + IWriteSession writeSession = spaceStorage.newWriteSession(NAKSHA_CONTEXT, true); + + // And: write request of unknown type + WriteRequest wr = new TestWriteRequest(); + + // Then: executing write request fails + assertThrows(UnsupportedOperationException.class, () -> writeSession.execute(wr)); + } + + @Test + void shouldDelegateToAdminWhenWritingCollections() { + // Given: new write session + IWriteSession writeSession = spaceStorage.newWriteSession(NAKSHA_CONTEXT, true); + + // And: admin storage that is able to create new write sessions + IWriteSession adminWriteSession = Mockito.mock(IWriteSession.class); + when(adminStorage.newWriteSession(NAKSHA_CONTEXT, true)).thenReturn(adminWriteSession); + + // And: write collection request + WriteCollections writeCollections = new WriteCollections(List.of("some collection")); + + // When: executing request + writeSession.execute(writeCollections); + + // Then: admin write session is used for execution + verify(adminWriteSession, times(1)).execute(writeCollections); + } + + @Test + void shouldTriggerEventPipelineWhenWritingToAdminSpaces() { + // Given: new write session + IWriteSession writeSession = spaceStorage.newWriteSession(NAKSHA_CONTEXT, true); + + // And: working event pipeline factory + EventPipeline eventPipeline = mock(EventPipeline.class); + when(eventPipelineFactory.eventPipeline()).thenReturn(eventPipeline); + + // And: write features request + WriteFeatures writeFeatures = new WriteFeatures(ADMIN_SPACE_ID); + + // When: executing request + writeSession.execute(writeFeatures); + + // Then: event pipeline is used to add corresponding event handler + verify(eventPipeline, times(1)).addEventHandler(eventHandler); + } + + @Test + void shouldFailWhenWritingToCustomSpaces() { + // Given: new write session + IWriteSession writeSession = spaceStorage.newWriteSession(NAKSHA_CONTEXT, true); + + // And: write features request + WriteFeatures writeFeatures = new WriteFeatures("unknown space"); + + // Then: executing request fails + assertThrows(UnsupportedOperationException.class, () -> writeSession.execute(writeFeatures)); + } + + @Test + void shouldNotSupportLockingFeatures() { + // Given: new write session + IWriteSession writeSession = spaceStorage.newWriteSession(NAKSHA_CONTEXT, true); + + // Then: locking should fail due to lack of support + assertThrows( + UnsupportedOperationException.class, + () -> writeSession.lockFeature("collection", "feature", 100, MILLISECONDS)); + } + + @Test + void shouldNotSupportLockingStorages() { + // Given: new write session + IWriteSession writeSession = spaceStorage.newWriteSession(NAKSHA_CONTEXT, true); + + // Then: locking should fail due to lack of support + assertThrows( + UnsupportedOperationException.class, () -> writeSession.lockStorage("lock_id", 100, MILLISECONDS)); + } + } + + class TestReadRequest extends ReadRequest {} + + class TestWriteRequest extends WriteRequest { + + TestWriteRequest() { + super(emptyList()); + } + } + + class TestNotification extends Notification {} +}