Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/add google cloud firestore support #85

Open
wants to merge 11 commits into
base: 1.7.x
Choose a base branch
from
6 changes: 6 additions & 0 deletions eclipsestore/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ dependencies {
compileOnly(mnAws.micronaut.aws.sdk.v2)
compileOnly(mn.micronaut.management)
compileOnly(mnSql.micronaut.jdbc)
compileOnly(libs.firestore.sdk)
compileOnly(libs.managed.eclipsestore.google.firestore)

api(libs.managed.eclipsestore.storage.embedded.configuration)

Expand Down Expand Up @@ -58,6 +60,10 @@ dependencies {
testImplementation(libs.azuresdk.blob)
testImplementation(libs.managed.eclipsestore.azure.storage)

// Firebase connector tests
testImplementation(libs.firestore.sdk)
testImplementation(libs.managed.eclipsestore.google.firestore)

testRuntimeOnly(mnLogging.logback.classic)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright 2017-2023 original authors
*
* 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
*
* https://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.
*/
package io.micronaut.eclipsestore.firestore;

import io.micronaut.context.annotation.EachProperty;
import io.micronaut.context.annotation.Parameter;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;

import java.util.Optional;

/**
* @author Simon Frauenschuh
* @since 1.7.0
*/
@Internal
@EachProperty("eclipsestore.firestore.storage")
class DefaultFirestoreStorageConfigurationProvider implements FirestoreStorageConfigurationProvider {

@NonNull
private Class<?> rootClass;

private final String name;

@Nullable
private String firestoreClientName;

@NonNull
private String logicalDirectory;

public DefaultFirestoreStorageConfigurationProvider(@Parameter String name) {
this.name = name;
}

@Override
@NonNull
public String getName() {
return name;
}

@Override
@NonNull
public Class<?> getRootClass() {
return this.rootClass;
}

/**
* Class of the Root Instance.
* <a href="https://docs.eclipsestore.io/manual/storage/root-instances.html">Root Instances</a>
* @param rootClass Class for the Root Instance.
*/
public void setRootClass(@NonNull Class<?> rootClass) {
this.rootClass = rootClass;
}

@Override
@NonNull
public Optional<String> getFirestoreClientName() {
return Optional.ofNullable(firestoreClientName);
}

/**
* The name qualifier of the defined Firestore to use.
* If unset, a Firestore with the same name as the storage will be used.
* If there is no bean with a name qualifier matching the storage name, the default client will be used.
*
* @param firestoreClientName the name qualifier of the S3Client to use
*/
public void setFirestoreClientName(@Nullable String firestoreClientName) {
this.firestoreClientName = firestoreClientName;
}

@NonNull
public String getLogicalDirectory() {
return logicalDirectory;
}

/**
* @param logicalDirectory Name of (logical) Firestore directory.
*/
public void setLogicalDirectory(@NonNull String logicalDirectory) {
this.logicalDirectory = logicalDirectory;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2017-2023 original authors
*
* 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
*
* https://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.
*/
package io.micronaut.eclipsestore.firestore;

import io.micronaut.core.annotation.NonNull;
import io.micronaut.eclipsestore.conf.RootClassConfigurationProvider;

import java.util.Optional;

/**
* @since 1.7.0
* @author Simon Frauenschuh
*/
public interface FirestoreStorageConfigurationProvider extends RootClassConfigurationProvider {

/**
* The name qualifier of the defined Firestore to use.
* If unset, a client with the same name as the storage will be used.
* If there is no bean with a name qualifier matching the storage name, the default client will be used.
*
* @return Returns the name qualifier of the S3Client to use.
*/
@NonNull
Optional<String> getFirestoreClientName();

/**
*
* @return Returns the name of the logical firestore directory to use.
*/
@NonNull
String getLogicalDirectory();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright 2017-2023 original authors
*
* 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
*
* https://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.
*/
package io.micronaut.eclipsestore.firestore;

import com.google.cloud.firestore.Firestore;
import io.micronaut.context.BeanContext;
import io.micronaut.context.annotation.EachBean;
import io.micronaut.context.annotation.Factory;
import io.micronaut.core.annotation.Internal;
import io.micronaut.inject.qualifiers.Qualifiers;
import jakarta.inject.Singleton;
import org.eclipse.store.afs.blobstore.types.BlobStoreFileSystem;
import org.eclipse.store.afs.googlecloud.firestore.types.GoogleCloudFirestoreConnector;
import org.eclipse.store.storage.embedded.types.EmbeddedStorage;
import org.eclipse.store.storage.embedded.types.EmbeddedStorageFoundation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Factory for an Firestore based EmbeddedStorageFoundation.
*
* @since 1.7.0
* @author Simon Frauenschuh
*/
@Internal
@Factory
class FirestoreStorageFoundationFactory {

private static final Logger LOG = LoggerFactory.getLogger(FirestoreStorageFoundationFactory.class);

/**
* @param ctx Bean Context.
* @param provider A {@link FirestoreStorageConfigurationProvider} provider.
* @return A {@link EmbeddedStorageFoundation}.
*/
@Singleton
@EachBean(FirestoreStorageConfigurationProvider.class)
EmbeddedStorageFoundation<?> createFoundation(
FirestoreStorageConfigurationProvider provider,
BeanContext ctx
) {
String firestoreClientName = provider.getFirestoreClientName().orElse(provider.getName());

if (LOG.isDebugEnabled()) {
LOG.debug("Looking for Firestore (client) named '{}'", firestoreClientName);
}

Firestore firestore = ctx.findBean(Firestore.class, Qualifiers.byName(firestoreClientName))
.orElseGet(() -> defaultClient(ctx, firestoreClientName));

if (LOG.isDebugEnabled()) {
LOG.debug("Got Firestore (client) {}", firestore);
}

BlobStoreFileSystem fileSystem = BlobStoreFileSystem.New(
GoogleCloudFirestoreConnector.Caching(firestore)
);

return EmbeddedStorage.Foundation(fileSystem.ensureDirectoryPath(provider.getLogicalDirectory()));
}

private Firestore defaultClient(BeanContext ctx, String firestoreClientName) {
if (LOG.isDebugEnabled()) {
LOG.debug("No Firestore (client) named '{}' found. Looking for a default", firestoreClientName);
}
return ctx.getBean(Firestore.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2017-2023 original authors
*
* 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
*
* https://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.
*/
/**
* EclipseStore Storage Target support for Firestore.
*
* @since 1.7.0
* @author Simon Frauenschuh
*/
@Requires(classes = GoogleCloudFirestoreConnector.class)
@Requires(beans = Firestore.class)
package io.micronaut.eclipsestore.firestore;

import com.google.cloud.firestore.Firestore;
import io.micronaut.context.annotation.Requires;
import org.eclipse.store.afs.googlecloud.firestore.types.GoogleCloudFirestoreConnector;

Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package io.micronaut.eclipsestore.firestore

import com.google.cloud.firestore.Firestore
import io.micronaut.context.BeanContext
import io.micronaut.context.annotation.*
import io.micronaut.core.annotation.NonNull
import io.micronaut.core.util.StringUtils
import io.micronaut.eclipsestore.BaseStorageSpec
import io.micronaut.eclipsestore.testutils.FirestoreLocal
import io.micronaut.inject.qualifiers.Qualifiers
import io.micronaut.test.extensions.spock.annotation.MicronautTest
import io.micronaut.test.support.TestPropertyProvider
import jakarta.inject.Inject
import jakarta.inject.Singleton
import org.eclipse.store.storage.embedded.types.EmbeddedStorageFoundation
import org.eclipse.store.storage.types.StorageManager
import org.testcontainers.DockerClientFactory
import spock.lang.AutoCleanup
import spock.lang.Shared

@spock.lang.Requires({ DockerClientFactory.instance().isDockerAvailable() })
@MicronautTest
@Property(name = "eclipsestore.firestore.storage.foo.logical-directory", value = FirestoreStorageSpec.LOGICAL_DIRECTORY)
@Property(name = "eclipsestore.firestore.storage.foo.root-class", value = 'io.micronaut.eclipsestore.BaseStorageSpec$Root')
@Property(name = "micronaut.metrics.enabled", value = StringUtils.FALSE)
@Property(name = "spec.type", value = "storage")
@Property(name = "spec.name", value = "FirestoreStorageSpec")
class FirestoreStorageSpec extends BaseStorageSpec implements TestPropertyProvider {

static final String LOGICAL_DIRECTORY = "eclipsestorefoo"

@Shared
@AutoCleanup
FirestoreLocal firestoreLocal = new FirestoreLocal()

@Inject
BeanContext beanContext

@Inject
@Shared
Firestore client

@Factory
@Requires(property = "spec.name", value = "FirestoreStorageSpec")
static class FireStoreClient {

@Replaces(Firestore)
@Singleton
Firestore buildClient() {
FirestoreLocal.firestoreClient()
}
}

@Inject
CustomerRepository customerRepository

@NonNull
@Override
Map<String, String> getProperties() {
return firestoreLocal.getProperties();
}

void "expected beans are created"() {
expect:
beanContext.containsBean(FirestoreStorageConfigurationProvider, Qualifiers.byName("foo"))
!beanContext.containsBean(Firestore, Qualifiers.byName("foo"))
beanContext.containsBean(Firestore)
beanContext.containsBean(EmbeddedStorageFoundation, Qualifiers.byName("foo"))
beanContext.getBeansOfType(StorageManager).size() == 1

when:
customerRepository.updateName("foo")

then:
customerRepository.name() == "foo"

when:
beanContext.getBean(CustomerRepository)

then:
noExceptionThrown()
}
}
3 changes: 3 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ groovy = "4.0.17"
kotlin = '1.9.24'
spock = "2.3-groovy-4.0"
managed-eclipsestore='1.4.0'
firestore = '3.25.0'
micronaut-aws = "4.6.0"
micronaut-cache = "4.3.1"
micronaut-azure = "5.5.1"
Expand All @@ -29,6 +30,7 @@ micronaut-test-resources = { module = "io.micronaut.testresources:micronaut-test
micronaut-validation = { module = "io.micronaut.validation:micronaut-validation-bom", version.ref = "micronaut-validation" }

managed-eclipsestore-azure-storage = { module = 'org.eclipse.store:afs-azure-storage', version.ref = 'managed-eclipsestore' }
managed-eclipsestore-google-firestore = { module = 'org.eclipse.store:afs-googlecloud-firestore', version.ref = 'managed-eclipsestore' }
managed-eclipsestore-aws-s3 = { module = 'org.eclipse.store:afs-aws-s3', version.ref = 'managed-eclipsestore' }
managed-eclipsestore-aws-dynamodb = { module = 'org.eclipse.store:afs-aws-dynamodb', version.ref = 'managed-eclipsestore' }
managed-eclipsestore-cache = { module = 'org.eclipse.store:cache', version.ref = 'managed-eclipsestore' }
Expand All @@ -49,6 +51,7 @@ junit-jupiter-engine = { module = 'org.junit.jupiter:junit-jupiter-engine' }
kotlin-stdlib = { module = 'org.jetbrains.kotlin:kotlin-stdlib-jdk8', version.ref = 'kotlin' }

azuresdk-blob = { module = 'com.azure:azure-storage-blob'}
firestore-sdk = { module = 'com.google.cloud:google-cloud-firestore', version.ref = 'firestore' }
awssdk-s3 = { module = 'software.amazon.awssdk:s3' }
awssdk-dynamodb = { module = 'software.amazon.awssdk:dynamodb' }
gradle-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
Loading
Loading