diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/src/main/java/org/springframework/ai/vectorstore/opensearch/autoconfigure/OpenSearchNonAwsCondition.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/src/main/java/org/springframework/ai/vectorstore/opensearch/autoconfigure/OpenSearchNonAwsCondition.java new file mode 100644 index 000000000..88692e7e9 --- /dev/null +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/src/main/java/org/springframework/ai/vectorstore/opensearch/autoconfigure/OpenSearchNonAwsCondition.java @@ -0,0 +1,72 @@ +/* + * Copyright 2023-2024 the original author or 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 org.springframework.ai.vectorstore.opensearch.autoconfigure; + +import org.springframework.boot.autoconfigure.condition.ConditionMessage; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * Condition that matches if either: + *
spring.ai.vectorstore.opensearch.aws.enabled is
+ * explicitly set to false.+ * This enables the non-AWS OpenSearch auto-configuration to be activated when the user + * disables AWS support via property or when AWS SDKs are not present, ensuring correct + * fallback behavior for non-AWS OpenSearch usage. + */ +public class OpenSearchNonAwsCondition extends SpringBootCondition { + + private static final String AWS_ENABLED_PROPERTY = "spring.ai.vectorstore.opensearch.aws.enabled"; + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { + // 1. If AWS property is set to false, match + String awsEnabled = context.getEnvironment().getProperty(AWS_ENABLED_PROPERTY); + if ("false".equalsIgnoreCase(awsEnabled)) { + return ConditionOutcome.match(ConditionMessage.forCondition("OpenSearchNonAwsCondition") + .because("Property 'spring.ai.vectorstore.opensearch.aws.enabled' is false")); + } + // 2. If AWS SDK classes are missing, match + boolean awsClassesPresent = isPresent("software.amazon.awssdk.auth.credentials.AwsCredentialsProvider") + && isPresent("software.amazon.awssdk.regions.Region") + && isPresent("software.amazon.awssdk.http.apache.ApacheHttpClient"); + if (!awsClassesPresent) { + return ConditionOutcome.match( + ConditionMessage.forCondition("OpenSearchNonAwsCondition").because("AWS SDK classes are missing")); + } + // 3. Otherwise, do not match + return ConditionOutcome.noMatch(ConditionMessage.forCondition("OpenSearchNonAwsCondition") + .because("AWS SDK classes are present and property is not false")); + } + + private boolean isPresent(String className) { + try { + Class.forName(className, false, getClass().getClassLoader()); + return true; + } + catch (ClassNotFoundException ex) { + return false; + } + } + +} diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/src/main/java/org/springframework/ai/vectorstore/opensearch/autoconfigure/OpenSearchVectorStoreAutoConfiguration.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/src/main/java/org/springframework/ai/vectorstore/opensearch/autoconfigure/OpenSearchVectorStoreAutoConfiguration.java index a41dee55e..74597d1dd 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/src/main/java/org/springframework/ai/vectorstore/opensearch/autoconfigure/OpenSearchVectorStoreAutoConfiguration.java +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/src/main/java/org/springframework/ai/vectorstore/opensearch/autoconfigure/OpenSearchVectorStoreAutoConfiguration.java @@ -94,8 +94,7 @@ public class OpenSearchVectorStoreAutoConfiguration { } @Configuration(proxyBeanMethods = false) - @ConditionalOnMissingClass({ "software.amazon.awssdk.regions.Region", - "software.amazon.awssdk.http.apache.ApacheHttpClient" }) + @org.springframework.context.annotation.Conditional(OpenSearchNonAwsCondition.class) static class OpenSearchConfiguration { @Bean @@ -134,8 +133,21 @@ public class OpenSearchVectorStoreAutoConfiguration { } + /** + * AWS OpenSearch configuration. + *
+ * This configuration is only enabled if AWS SDK classes are present on the classpath + * and the property {@code spring.ai.vectorstore.opensearch.aws.enabled} is set + * to {@code true} (default: true). + *
+ * Set {@code spring.ai.vectorstore.opensearch.aws.enabled=false} to disable
+ * AWS-specific OpenSearch configuration when AWS SDK is present for other services
+ * (e.g., S3).
+ */
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ AwsCredentialsProvider.class, Region.class, ApacheHttpClient.class })
+ @ConditionalOnProperty(name = "spring.ai.vectorstore.opensearch.aws.enabled", havingValue = "true",
+ matchIfMissing = true)
static class AwsOpenSearchConfiguration {
@Bean
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/src/test/java/org/springframework/ai/vectorstore/opensearch/autoconfigure/OpenSearchVectorStoreNonAwsFallbackIT.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/src/test/java/org/springframework/ai/vectorstore/opensearch/autoconfigure/OpenSearchVectorStoreNonAwsFallbackIT.java
new file mode 100644
index 000000000..ea51b6d0f
--- /dev/null
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/src/test/java/org/springframework/ai/vectorstore/opensearch/autoconfigure/OpenSearchVectorStoreNonAwsFallbackIT.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2023-2024 the original author or 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 org.springframework.ai.vectorstore.opensearch.autoconfigure;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import org.opensearch.testcontainers.OpensearchContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
+
+import org.springframework.ai.document.Document;
+import org.springframework.ai.embedding.EmbeddingModel;
+import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
+import org.springframework.ai.transformers.TransformersEmbeddingModel;
+import org.springframework.ai.vectorstore.opensearch.OpenSearchVectorStore;
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.DefaultResourceLoader;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Testcontainers
+class OpenSearchVectorStoreNonAwsFallbackIT {
+
+ @Container
+ private static final OpensearchContainer> opensearchContainer = new OpensearchContainer<>(
+ DockerImageName.parse("opensearchproject/opensearch:2.13.0"));
+
+ private static final String DOCUMENT_INDEX = "nonaws-spring-ai-document-index";
+
+ private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+ .withConfiguration(AutoConfigurations.of(OpenSearchVectorStoreAutoConfiguration.class,
+ SpringAiRetryAutoConfiguration.class))
+ .withUserConfiguration(Config.class)
+ .withPropertyValues("spring.ai.vectorstore.opensearch.aws.enabled=false",
+ "spring.ai.vectorstore.opensearch.uris=" + opensearchContainer.getHttpHostAddress(),
+ "spring.ai.vectorstore.opensearch.indexName=" + DOCUMENT_INDEX,
+ "spring.ai.vectorstore.opensearch.mappingJson={\"properties\":{\"embedding\":{\"type\":\"knn_vector\",\"dimension\":384}}}");
+
+ private List