diff --git a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/DefaultFirebaseCredentialsFactory.java b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/DefaultFirebaseCredentialsFactory.java index 8a704bd..94eede6 100644 --- a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/DefaultFirebaseCredentialsFactory.java +++ b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/DefaultFirebaseCredentialsFactory.java @@ -3,11 +3,15 @@ import com.google.auth.oauth2.GoogleCredentials; import lombok.SneakyThrows; import org.springframework.core.io.ResourceLoader; +import org.springframework.util.StringUtils; public record DefaultFirebaseCredentialsFactory(ResourceLoader resourceLoader) implements FirebaseCredentialsFactory { @SneakyThrows @Override public GoogleCredentials create(FirebaseProperties properties) { + if (!StringUtils.hasText(properties.getCredentials())) { + return GoogleCredentials.getApplicationDefault(); + } var resource = resourceLoader.getResource(properties.getCredentials()); var in = resource.getInputStream(); return GoogleCredentials.fromStream(in); diff --git a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/DefaultFirebaseOptionsFactory.java b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/DefaultFirebaseOptionsFactory.java index 246e6fe..7569b65 100644 --- a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/DefaultFirebaseOptionsFactory.java +++ b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/DefaultFirebaseOptionsFactory.java @@ -9,9 +9,15 @@ public FirebaseOptions create(FirebaseProperties properties) { var mapper = PropertyMapper.get().alwaysApplyingWhenNonNull(); var ops = FirebaseOptions.builder(); mapper.from(properties.getDatabaseAuthVariableOverride()).to(ops::setDatabaseAuthVariableOverride); - mapper.from(properties.getDatabaseUrl()).to(ops::setDatabaseUrl); + mapper.from(properties.getDb()) + .as(FirebaseDatabaseProperties::getUrl) + .whenHasText() + .to(ops::setDatabaseUrl); mapper.from(properties.getProjectId()).to(ops::setProjectId); - mapper.from(properties.getStorageBucket()).to(ops::setStorageBucket); + mapper.from(properties.getStorage()) + .as(StorageProperties::getBucket) + .whenHasText() + .to(ops::setStorageBucket); mapper.from(properties.getServiceAccountId()).to(ops::setServiceAccountId); mapper.from(credsFactory.create(properties)).to(ops::setCredentials); mapper.from(properties.getConnectTimeout()).to(ops::setConnectTimeout); diff --git a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirebaseDatabaseProperties.java b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirebaseDatabaseProperties.java index 29ba9fd..679e4a9 100644 --- a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirebaseDatabaseProperties.java +++ b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirebaseDatabaseProperties.java @@ -11,4 +11,5 @@ @Builder public class FirebaseDatabaseProperties { private boolean enabled; + private String url; } diff --git a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirebaseProperties.java b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirebaseProperties.java index ccce450..ba821ab 100644 --- a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirebaseProperties.java +++ b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirebaseProperties.java @@ -5,7 +5,6 @@ import lombok.Data; import lombok.NoArgsConstructor; -import java.util.HashMap; import java.util.Map; import static com.google.firebase.FirebaseApp.DEFAULT_APP_NAME; @@ -21,17 +20,21 @@ public class FirebaseProperties { @lombok.Builder.Default private String name = DEFAULT_APP_NAME; - @lombok.Builder.Default - private Map databaseAuthVariableOverride = new HashMap<>(); - private String databaseUrl; + private Map databaseAuthVariableOverride; private String projectId; - private String storageBucket; private String serviceAccountId; private String credentials; private Integer connectTimeout; private Integer readTimeout; private Integer writeTimeout; + @lombok.Builder.Default private FirebaseAuthProperties auth = new FirebaseAuthProperties(); + @lombok.Builder.Default private FirebaseDatabaseProperties db = new FirebaseDatabaseProperties(); + @lombok.Builder.Default private FirebaseMessagingProperties messaging = new FirebaseMessagingProperties(); + @lombok.Builder.Default + private StorageProperties storage = new StorageProperties(); + @lombok.Builder.Default + private FirestoreProperties firestore = new FirestoreProperties(); } diff --git a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirestoreProperties.java b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirestoreProperties.java new file mode 100644 index 0000000..512f864 --- /dev/null +++ b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirestoreProperties.java @@ -0,0 +1,14 @@ +package io.github.justedlev.firebase; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class FirestoreProperties { + private boolean enabled; +} diff --git a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/StorageProperties.java b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/StorageProperties.java new file mode 100644 index 0000000..3ef42ea --- /dev/null +++ b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/StorageProperties.java @@ -0,0 +1,15 @@ +package io.github.justedlev.firebase; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class StorageProperties { + private boolean enabled; + private String bucket; +} diff --git a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/autoconfigure/FirebaseAppsConfiguredCondition.java b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/autoconfigure/FirebaseAppsConfiguredCondition.java index 9ebfcf1..c088e8d 100644 --- a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/autoconfigure/FirebaseAppsConfiguredCondition.java +++ b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/autoconfigure/FirebaseAppsConfiguredCondition.java @@ -1,6 +1,5 @@ package io.github.justedlev.firebase.autoconfigure; -import io.github.justedlev.firebase.FirebaseProperties; import io.github.justedlev.firebase.config.FirebaseConfigurationProperties; import org.jspecify.annotations.NonNull; import org.springframework.boot.autoconfigure.condition.ConditionMessage; @@ -9,18 +8,18 @@ import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.type.AnnotatedTypeMetadata; import java.util.Collections; -import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Predicate; -import java.util.stream.Collectors; class FirebaseAppsConfiguredCondition extends SpringBootCondition { - private static final Bindable<@NonNull Map> BINDABLE = - Bindable.mapOf(String.class, FirebaseProperties.class); + private static final String MATCH_MSG = "registered firebase apps"; + private static final Bindable<@NonNull FirebaseConfigurationProperties> BINDABLE = + Bindable.of(FirebaseConfigurationProperties.class); @NonNull @Override @@ -28,23 +27,26 @@ public ConditionOutcome getMatchOutcome(@NonNull ConditionContext context, @NonN var exclude = getExclude(metadata); var message = ConditionMessage.forCondition("Firebase Apps Configured Condition"); var appNames = Binder.get(context.getEnvironment()) - .bind(FirebaseConfigurationProperties.PREFIX + ".apps", BINDABLE) - .orElse(Collections.emptyMap()) + .bind(FirebaseConfigurationProperties.PREFIX, BINDABLE) + .orElseGet(FirebaseConfigurationProperties::new) + .getApps() .keySet() .stream() .filter(Predicate.not(exclude::contains)) - .collect(Collectors.joining(", ")); - if (!appNames.isEmpty()) { - return ConditionOutcome.match(message.foundExactly("registered firebase apps " + appNames)); + .toList(); + + if (appNames.isEmpty()) { + return ConditionOutcome.noMatch(message.notAvailable(MATCH_MSG)); } - return ConditionOutcome.noMatch(message.notAvailable("registered firebase apps")); + + return ConditionOutcome.match(message.found(MATCH_MSG).items(ConditionMessage.Style.QUOTE, appNames)); } private Set getExclude(AnnotatedTypeMetadata metadata) { - return Optional.of(metadata) - .map(v -> v.getAnnotationAttributes(ConditionalOnFirebaseAppsProperties.class.getName())) - .map(v -> v.get("exclude")) - .map(String[].class::cast) + return Optional.of(metadata.getAnnotations()) + .map(v -> v.get(ConditionalOnFirebaseAppsProperties.class)) + .map(MergedAnnotation::synthesize) + .map(ConditionalOnFirebaseAppsProperties::exclude) .map(Set::of) .orElseGet(Collections::emptySet); } diff --git a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/autoconfigure/FirebaseAutoConfiguration.java b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/autoconfigure/FirebaseAutoConfiguration.java index 7aa851c..5b87fd0 100644 --- a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/autoconfigure/FirebaseAutoConfiguration.java +++ b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/autoconfigure/FirebaseAutoConfiguration.java @@ -1,7 +1,10 @@ package io.github.justedlev.firebase.autoconfigure; +import com.google.cloud.firestore.Firestore; import com.google.firebase.FirebaseApp; import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.cloud.FirestoreClient; +import com.google.firebase.cloud.StorageClient; import com.google.firebase.database.FirebaseDatabase; import com.google.firebase.messaging.FirebaseMessaging; import io.github.justedlev.firebase.DefaultFirebaseCredentialsFactory; @@ -57,7 +60,7 @@ public FirebaseApp defaultFirebaseApp(FirebaseOptionsFactory optionsFactory, Fir @Bean @ConditionalOnBean(name = "defaultFirebaseApp") @ConditionalOnBooleanProperty(prefix = PREFIX + ".apps.default.db", value = "enabled", matchIfMissing = true) - @ConditionalOnProperty(prefix = PREFIX, value = "apps.default.database-url") + @ConditionalOnProperty(prefix = PREFIX, value = "apps.default.db.url") public FirebaseDatabase defaultFirebaseDatabase(FirebaseApp firebaseApp) { return FirebaseDatabase.getInstance(firebaseApp); } @@ -75,4 +78,19 @@ public FirebaseAuth defaultFirebaseAuth(FirebaseApp firebaseApp) { public FirebaseMessaging defaultFirebaseMessaging(FirebaseApp firebaseApp) { return FirebaseMessaging.getInstance(firebaseApp); } + + @Bean + @ConditionalOnBean(name = "defaultFirebaseApp") + @ConditionalOnBooleanProperty(prefix = PREFIX + ".apps.default.storage", value = "enabled", matchIfMissing = true) + @ConditionalOnProperty(prefix = PREFIX, value = "apps.default.storage.bucket") + public StorageClient defaultStorageClient(FirebaseApp firebaseApp) { + return StorageClient.getInstance(firebaseApp); + } + + @Bean + @ConditionalOnBean(name = "defaultFirebaseApp") + @ConditionalOnBooleanProperty(prefix = PREFIX + ".apps.default.firestore", value = "enabled") + public Firestore defaultFirestore(FirebaseApp firebaseApp) { + return FirestoreClient.getFirestore(firebaseApp); + } } diff --git a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/config/FirebaseConfigurationProperties.java b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/config/FirebaseConfigurationProperties.java index c13f6c0..a5903b9 100644 --- a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/config/FirebaseConfigurationProperties.java +++ b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/config/FirebaseConfigurationProperties.java @@ -30,9 +30,4 @@ public class FirebaseConfigurationProperties { public FirebaseProperties getDefaultApp() { return apps.get(DEFAULT_APP_NAME); } - - public FirebaseConfigurationProperties setDefaultApp(FirebaseProperties properties) { - apps.put(DEFAULT_APP_NAME, properties); - return this; - } } diff --git a/firebase-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/firebase-spring-boot/src/main/resources/META-INF/spring/AutoConfiguration.imports similarity index 100% rename from firebase-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports rename to firebase-spring-boot/src/main/resources/META-INF/spring/AutoConfiguration.imports diff --git a/firebase-spring-boot/src/main/resources/application.yaml b/firebase-spring-boot/src/main/resources/application.yaml index ee70d79..1c66f17 100644 --- a/firebase-spring-boot/src/main/resources/application.yaml +++ b/firebase-spring-boot/src/main/resources/application.yaml @@ -1,5 +1,4 @@ firebase: apps: default: - database-url: https://${firebase.apps.default.project-id}-default-rtdb.firebaseio.com - credentials: file:${user.home}/.firebase/${firebase.apps.default.project-id}.json + credentials: file:${user.home}/.config/gcloud/${firebase.apps.default.project-id}.json diff --git a/firebase-spring-boot/src/test/java/io/github/justedlev/firebase/FirebaseAutoConfigurationTest.java b/firebase-spring-boot/src/test/java/io/github/justedlev/firebase/FirebaseAutoConfigurationTest.java index 123d21e..f2f5cd4 100644 --- a/firebase-spring-boot/src/test/java/io/github/justedlev/firebase/FirebaseAutoConfigurationTest.java +++ b/firebase-spring-boot/src/test/java/io/github/justedlev/firebase/FirebaseAutoConfigurationTest.java @@ -1,7 +1,9 @@ package io.github.justedlev.firebase; +import com.google.cloud.firestore.Firestore; import com.google.firebase.FirebaseApp; import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.cloud.StorageClient; import com.google.firebase.database.FirebaseDatabase; import com.google.firebase.messaging.FirebaseMessaging; import io.github.justedlev.firebase.autoconfigure.FirebaseAutoConfiguration; @@ -74,31 +76,19 @@ void whenAllEnabled_thenBeansCreated() { .withPropertyValues( "firebase.enabled=true", "firebase.apps.default.project-id=default-project", - "firebase.apps.default.database-url=http://localhost:0", + "firebase.apps.default.db.url=http://localhost:0", "firebase.apps.default.auth.enabled=true", - "firebase.apps.default.messaging.enabled=true" + "firebase.apps.default.messaging.enabled=true", + "firebase.apps.default.storage.bucket=default-bucket", + "firebase.apps.default.firestore.enabled=true" ) .run(context -> { assertThat(context).hasSingleBean(FirebaseApp.class); assertThat(context).hasSingleBean(FirebaseDatabase.class); assertThat(context).hasSingleBean(FirebaseAuth.class); assertThat(context).hasSingleBean(FirebaseMessaging.class); - }); - } - - @Test - void whenDatabaseUrlIsSet_thenFirebaseDatabaseBeanCreated() { - new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(FirebaseAutoConfiguration.class)) - .withUserConfiguration(FirebaseTestConfiguration.class) - .withPropertyValues( - "firebase.enabled=true", - "firebase.apps.default.project-id=default-project", - "firebase.apps.default.database-url=http://localhost:0" - ) - .run(context -> { - assertThat(context).hasSingleBean(FirebaseApp.class); - assertThat(context).hasSingleBean(FirebaseDatabase.class); + assertThat(context).hasSingleBean(StorageClient.class); + assertThat(context).hasSingleBean(Firestore.class); }); }