Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@
@Builder
public class FirebaseDatabaseProperties {
private boolean enabled;
private String url;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,17 +20,21 @@
public class FirebaseProperties {
@lombok.Builder.Default
private String name = DEFAULT_APP_NAME;
@lombok.Builder.Default
private Map<String, Object> databaseAuthVariableOverride = new HashMap<>();
private String databaseUrl;
private Map<String, Object> 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();
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -9,42 +8,45 @@
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<String, FirebaseProperties>> 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
public ConditionOutcome getMatchOutcome(@NonNull ConditionContext context, @NonNull AnnotatedTypeMetadata metadata) {
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<String> 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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
3 changes: 1 addition & 2 deletions firebase-spring-boot/src/main/resources/application.yaml
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
});
}

Expand Down
Loading