diff --git a/spring-vault-core/pom.xml b/spring-vault-core/pom.xml
index 2d859efb..f9e8d403 100644
--- a/spring-vault-core/pom.xml
+++ b/spring-vault-core/pom.xml
@@ -23,6 +23,11 @@
../src/main/resources
META-INF
+
+
+ src/main/resources/META-INF
+ META-INF
+
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/annotation/PropertySourceAotProcessor.java b/spring-vault-core/src/main/java/org/springframework/vault/annotation/PropertySourceAotProcessor.java
new file mode 100644
index 00000000..deca2412
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/annotation/PropertySourceAotProcessor.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2023 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
+ *
+ * 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.
+ */
+package org.springframework.vault.annotation;
+
+import java.util.Map.Entry;
+import java.util.function.Predicate;
+
+import org.springframework.aot.generate.GenerationContext;
+import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
+import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
+import org.springframework.beans.factory.aot.BeanRegistrationCode;
+import org.springframework.beans.factory.aot.BeanRegistrationCodeFragments;
+import org.springframework.beans.factory.aot.BeanRegistrationCodeFragmentsDecorator;
+import org.springframework.beans.factory.config.BeanReference;
+import org.springframework.beans.factory.config.ConstructorArgumentValues;
+import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
+import org.springframework.beans.factory.config.RuntimeBeanReference;
+import org.springframework.beans.factory.support.RegisteredBean;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.javapoet.CodeBlock;
+import org.springframework.lang.Nullable;
+import org.springframework.util.ClassUtils;
+import org.springframework.vault.core.lease.domain.RequestedSecret;
+import org.springframework.vault.core.lease.domain.RequestedSecret.Mode;
+import org.springframework.vault.core.util.PropertyTransformers;
+import org.springframework.vault.core.util.PropertyTransformers.KeyPrefixPropertyTransformer;
+import org.springframework.vault.core.util.PropertyTransformers.NoOpPropertyTransformer;
+
+/**
+ * AOT processor to serialize
+ *
+ * @author Mark Paluch
+ * @since 3.0.1
+ */
+class PropertySourceAotProcessor implements BeanRegistrationAotProcessor {
+
+ @Override
+ public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
+
+ if (registeredBean.getBeanClass() == org.springframework.vault.core.env.LeaseAwareVaultPropertySource.class) {
+ return BeanRegistrationAotContribution.withCustomCodeFragments(AotContribution::new);
+ }
+
+ if (registeredBean.getBeanClass() == org.springframework.vault.core.env.VaultPropertySource.class) {
+ return BeanRegistrationAotContribution.withCustomCodeFragments(AotContribution::new);
+ }
+
+ return null;
+ }
+
+ static class AotContribution extends BeanRegistrationCodeFragmentsDecorator {
+
+ protected AotContribution(BeanRegistrationCodeFragments delegate) {
+ super(delegate);
+ }
+
+ @Override
+ public CodeBlock generateSetBeanDefinitionPropertiesCode(GenerationContext generationContext,
+ BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition,
+ Predicate attributeFilter) {
+
+ CodeBlock.Builder code = CodeBlock.builder();
+
+ ConstructorArgumentValues values = beanDefinition.getConstructorArgumentValues();
+
+ for (Entry entry : values.getIndexedArgumentValues().entrySet()) {
+
+ CodeBlock renderedValue = render(entry.getValue().getValue());
+ code.addStatement("$N.getConstructorArgumentValues().addIndexedArgumentValue($L, $L)",
+ BeanRegistrationCodeFragments.BEAN_DEFINITION_VARIABLE, entry.getKey(), renderedValue);
+ }
+
+ return code.build();
+ }
+
+ private static CodeBlock render(@Nullable Object value) {
+
+ if (value instanceof RuntimeBeanReference runtimeBeanReference
+ && runtimeBeanReference.getBeanType() != null) {
+ return CodeBlock.of("new $T($T.class)", RuntimeBeanReference.class, runtimeBeanReference.getBeanType());
+ }
+
+ if (value instanceof BeanReference beanReference) {
+ return CodeBlock.of("new $T($S)", RuntimeBeanReference.class, beanReference.getBeanName());
+ }
+
+ if (value instanceof String) {
+ return CodeBlock.of("$S", value.toString());
+ }
+
+ if (value == null || ClassUtils.isPrimitiveOrWrapper(value.getClass())) {
+ return CodeBlock.of("$L", value != null ? value.toString() : "null");
+ }
+
+ if (value instanceof NoOpPropertyTransformer) {
+ return CodeBlock.of("$T.$N()", PropertyTransformers.class, "noop");
+ }
+
+ if (value instanceof KeyPrefixPropertyTransformer kpt) {
+ return CodeBlock.of("$T.$N($S)", PropertyTransformers.class, "propertyNamePrefix",
+ kpt.getPropertyNamePrefix());
+ }
+
+ if (value instanceof RequestedSecret rs) {
+ return CodeBlock.of("$T.$N($S)", RequestedSecret.class,
+ rs.getMode() == Mode.ROTATE ? "rotating" : "renewable", rs.getPath());
+
+ }
+
+ throw new IllegalArgumentException("Unsupported value type: " + value.getClass());
+ }
+
+ }
+
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/annotation/VaultPropertySourceRegistrar.java b/spring-vault-core/src/main/java/org/springframework/vault/annotation/VaultPropertySourceRegistrar.java
index 824ef393..cf5cbf68 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/annotation/VaultPropertySourceRegistrar.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/annotation/VaultPropertySourceRegistrar.java
@@ -135,6 +135,7 @@ class VaultPropertySourceRegistrar
AbstractBeanDefinition beanDefinition = createBeanDefinition(ref, renewal, propertyTransformer,
ignoreSecretNotFound, potentiallyResolveRequiredPlaceholders(propertyPath));
+ beanDefinition.setSource(annotationMetadata.getClassName());
do {
String beanName = "vaultPropertySource#" + counter;
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/aot/VaultRuntimeHints.java b/spring-vault-core/src/main/java/org/springframework/vault/aot/VaultRuntimeHints.java
new file mode 100644
index 00000000..082a6a5f
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/aot/VaultRuntimeHints.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2023 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
+ *
+ * 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.
+ */
+package org.springframework.vault.aot;
+
+import java.io.IOException;
+import java.util.stream.Stream;
+
+import org.springframework.aot.hint.MemberCategory;
+import org.springframework.aot.hint.ReflectionHints;
+import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.aot.hint.RuntimeHintsRegistrar;
+import org.springframework.aot.hint.TypeReference;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Runtime hints for Spring Vault.
+ *
+ * @author Mark Paluch
+ * @since 3.0.1
+ */
+class VaultRuntimeHints implements RuntimeHintsRegistrar {
+
+ private final CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
+
+ @Override
+ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
+
+ PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(classLoader);
+
+ ReflectionHints reflection = hints.reflection();
+ MemberCategory[] dataObjectCategories = new MemberCategory[] { MemberCategory.DECLARED_FIELDS,
+ MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
+ MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS, MemberCategory.INTROSPECT_DECLARED_METHODS };
+ try {
+ Resource[] resources = resolver.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ + ClassUtils.convertClassNameToResourcePath("org.springframework.vault.support") + "/*.class");
+
+ for (Resource resource : resources) {
+
+ MetadataReader metadataReader = factory.getMetadataReader(resource);
+ String className = metadataReader.getClassMetadata().getClassName();
+
+ if (className.contains("-")) {
+ continue;
+ }
+
+ reflection.registerType(TypeReference.of(className), dataObjectCategories);
+ }
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ Stream.of("org.springframework.vault.core.VaultSysTemplate$GetMounts$VaultMountsResponse",
+ "org.springframework.vault.core.VaultVersionedKeyValueTemplate$VersionedResponse",
+ "org.springframework.vault.core.ReactiveVaultTemplate$VaultListResponse",
+ "org.springframework.vault.core.VaultListResponse",
+
+ "org.springframework.vault.core.VaultTransitTemplate$RawTransitKeyImpl",
+ "org.springframework.vault.core.VaultTransitTemplate$VaultTransitKeyImpl",
+
+ "org.springframework.vault.core.VaultSysTemplate$GetMounts",
+ "org.springframework.vault.core.VaultSysTemplate$GetUnsealStatus",
+ "org.springframework.vault.core.VaultSysTemplate$Health",
+ "org.springframework.vault.core.VaultSysTemplate$Seal",
+ "org.springframework.vault.core.VaultSysTemplate$VaultHealthImpl",
+ "org.springframework.vault.core.VaultSysTemplate$VaultInitializationResponseImpl",
+ "org.springframework.vault.core.VaultSysTemplate$VaultUnsealStatusImpl",
+
+ "org.springframework.vault.core.VaultVersionedKeyValueTemplate$VersionedResponse")
+ .forEach(cls -> reflection.registerType(TypeReference.of(cls), dataObjectCategories));
+
+ reflection.registerTypeIfPresent(classLoader, "com.google.api.client.json.jackson2.JacksonFactory",
+ MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
+
+ reflection.registerTypeIfPresent(classLoader, "com.google.api.client.json.gson.GsonFactory",
+ MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
+
+ }
+
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/aot/package-info.java b/spring-vault-core/src/main/java/org/springframework/vault/aot/package-info.java
new file mode 100644
index 00000000..319ab0f4
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/aot/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * Ahead-of-Time support.
+ */
+@org.springframework.lang.NonNullApi
+@org.springframework.lang.NonNullFields
+package org.springframework.vault.aot;
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpConnectorFactory.java b/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpConnectorFactory.java
index a8eb7cb9..5dadad4f 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpConnectorFactory.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpConnectorFactory.java
@@ -65,28 +65,14 @@ import static org.springframework.vault.client.ClientHttpRequestFactoryFactory.*
*/
public class ClientHttpConnectorFactory {
- private static final boolean REACTOR_NETTY_PRESENT = isPresent("reactor.netty.http.client.HttpClient");
+ private static final boolean REACTOR_NETTY_PRESENT = ClassUtils.isPresent("reactor.netty.http.client.HttpClient",
+ ClientHttpConnectorFactory.class.getClassLoader());
- private static final boolean HTTP_COMPONENTS_PRESENT = isPresent("org.apache.hc.client5.http.impl.async");
+ private static final boolean HTTP_COMPONENTS_PRESENT = ClassUtils.isPresent("org.apache.hc.client5.http.impl.async",
+ ClientHttpConnectorFactory.class.getClassLoader());
- private static final boolean JETTY_PRESENT = isPresent("org.eclipse.jetty.client.HttpClient");
-
- /**
- * Checks for presence of all {@code classNames} using this class' classloader.
- * @param classNames
- * @return {@literal true} if all classes are present; {@literal false} if at least
- * one class cannot be found.
- */
- private static boolean isPresent(String... classNames) {
-
- for (String className : classNames) {
- if (!ClassUtils.isPresent(className, ClientHttpConnectorFactory.class.getClassLoader())) {
- return false;
- }
- }
-
- return true;
- }
+ private static final boolean JETTY_PRESENT = ClassUtils.isPresent("org.eclipse.jetty.client.HttpClient",
+ ClientHttpConnectorFactory.class.getClassLoader());
/**
* Create a {@link ClientHttpConnector} for the given {@link ClientOptions} and
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpRequestFactoryFactory.java b/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpRequestFactoryFactory.java
index 86c7e026..2e48e1cb 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpRequestFactoryFactory.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpRequestFactoryFactory.java
@@ -89,27 +89,12 @@ public class ClientHttpRequestFactoryFactory {
@SuppressWarnings("FieldMayBeFinal") // allow setting via reflection.
private static Log logger = LogFactory.getLog(ClientHttpRequestFactoryFactory.class);
- private static final boolean HTTP_COMPONENTS_PRESENT = isPresent(
- "org.apache.hc.client5.http.impl.classic.HttpClientBuilder");
+ private static final boolean HTTP_COMPONENTS_PRESENT = ClassUtils.isPresent(
+ "org.apache.hc.client5.http.impl.classic.HttpClientBuilder",
+ ClientHttpRequestFactoryFactory.class.getClassLoader());
- private static final boolean OKHTTP3_PRESENT = isPresent("okhttp3.OkHttpClient");
-
- /**
- * Checks for presence of all {@code classNames} using this class' classloader.
- * @param classNames
- * @return {@literal true} if all classes are present; {@literal false} if at least
- * one class cannot be found.
- */
- private static boolean isPresent(String... classNames) {
-
- for (String className : classNames) {
- if (!ClassUtils.isPresent(className, ClientHttpRequestFactoryFactory.class.getClassLoader())) {
- return false;
- }
- }
-
- return true;
- }
+ private static final boolean OKHTTP3_PRESENT = ClassUtils.isPresent("okhttp3.OkHttpClient",
+ ClientHttpRequestFactoryFactory.class.getClassLoader());
/**
* Create a {@link ClientHttpRequestFactory} for the given {@link ClientOptions} and
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/util/PropertyTransformers.java b/spring-vault-core/src/main/java/org/springframework/vault/core/util/PropertyTransformers.java
index 2724e89a..5316aef7 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/core/util/PropertyTransformers.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/core/util/PropertyTransformers.java
@@ -57,7 +57,7 @@ public abstract class PropertyTransformers {
* {@link PropertyTransformer} that passes the given properties through without
* returning changed properties.
*/
- static class NoOpPropertyTransformer implements PropertyTransformer {
+ public static class NoOpPropertyTransformer implements PropertyTransformer {
static NoOpPropertyTransformer INSTANCE = new NoOpPropertyTransformer();
@@ -118,7 +118,7 @@ public abstract class PropertyTransformers {
/**
* {@link PropertyTransformer} that adds a prefix to each key name.
*/
- static class KeyPrefixPropertyTransformer implements PropertyTransformer {
+ public static class KeyPrefixPropertyTransformer implements PropertyTransformer {
private final String propertyNamePrefix;
@@ -129,6 +129,10 @@ public abstract class PropertyTransformers {
this.propertyNamePrefix = propertyNamePrefix;
}
+ public String getPropertyNamePrefix() {
+ return propertyNamePrefix;
+ }
+
/**
* Create a new {@link KeyPrefixPropertyTransformer} that adds a prefix to each
* key name.
diff --git a/spring-vault-core/src/main/resources/META-INF/spring/aot.factories b/spring-vault-core/src/main/resources/META-INF/spring/aot.factories
new file mode 100644
index 00000000..a7c09072
--- /dev/null
+++ b/spring-vault-core/src/main/resources/META-INF/spring/aot.factories
@@ -0,0 +1,3 @@
+org.springframework.aot.hint.RuntimeHintsRegistrar=org.springframework.vault.aot.VaultRuntimeHints
+
+org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=org.springframework.vault.annotation.PropertySourceAotProcessor