Skip to content

Commit 5570312

Browse files
committed
Add native image hints and AOT pre-processor.
Closes gh-747
1 parent 697d940 commit 5570312

File tree

9 files changed

+259
-42
lines changed

9 files changed

+259
-42
lines changed

spring-vault-core/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@
2323
<directory>../src/main/resources</directory>
2424
<targetPath>META-INF</targetPath>
2525
</resource>
26+
27+
<resource>
28+
<directory>src/main/resources/META-INF</directory>
29+
<targetPath>META-INF</targetPath>
30+
</resource>
2631
</resources>
2732
<plugins>
2833
<plugin>
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.vault.annotation;
17+
18+
import java.util.Map.Entry;
19+
import java.util.function.Predicate;
20+
21+
import org.springframework.aot.generate.GenerationContext;
22+
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
23+
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
24+
import org.springframework.beans.factory.aot.BeanRegistrationCode;
25+
import org.springframework.beans.factory.aot.BeanRegistrationCodeFragments;
26+
import org.springframework.beans.factory.aot.BeanRegistrationCodeFragmentsDecorator;
27+
import org.springframework.beans.factory.config.BeanReference;
28+
import org.springframework.beans.factory.config.ConstructorArgumentValues;
29+
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
30+
import org.springframework.beans.factory.config.RuntimeBeanReference;
31+
import org.springframework.beans.factory.support.RegisteredBean;
32+
import org.springframework.beans.factory.support.RootBeanDefinition;
33+
import org.springframework.javapoet.CodeBlock;
34+
import org.springframework.lang.Nullable;
35+
import org.springframework.util.ClassUtils;
36+
import org.springframework.vault.core.lease.domain.RequestedSecret;
37+
import org.springframework.vault.core.lease.domain.RequestedSecret.Mode;
38+
import org.springframework.vault.core.util.PropertyTransformers;
39+
import org.springframework.vault.core.util.PropertyTransformers.KeyPrefixPropertyTransformer;
40+
import org.springframework.vault.core.util.PropertyTransformers.NoOpPropertyTransformer;
41+
42+
/**
43+
* AOT processor to serialize
44+
*
45+
* @author Mark Paluch
46+
* @since 3.0.1
47+
*/
48+
class PropertySourceAotProcessor implements BeanRegistrationAotProcessor {
49+
50+
@Override
51+
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
52+
53+
if (registeredBean.getBeanClass() == org.springframework.vault.core.env.LeaseAwareVaultPropertySource.class) {
54+
return BeanRegistrationAotContribution.withCustomCodeFragments(AotContribution::new);
55+
}
56+
57+
if (registeredBean.getBeanClass() == org.springframework.vault.core.env.VaultPropertySource.class) {
58+
return BeanRegistrationAotContribution.withCustomCodeFragments(AotContribution::new);
59+
}
60+
61+
return null;
62+
}
63+
64+
static class AotContribution extends BeanRegistrationCodeFragmentsDecorator {
65+
66+
protected AotContribution(BeanRegistrationCodeFragments delegate) {
67+
super(delegate);
68+
}
69+
70+
@Override
71+
public CodeBlock generateSetBeanDefinitionPropertiesCode(GenerationContext generationContext,
72+
BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition,
73+
Predicate<String> attributeFilter) {
74+
75+
CodeBlock.Builder code = CodeBlock.builder();
76+
77+
ConstructorArgumentValues values = beanDefinition.getConstructorArgumentValues();
78+
79+
for (Entry<Integer, ValueHolder> entry : values.getIndexedArgumentValues().entrySet()) {
80+
81+
CodeBlock renderedValue = render(entry.getValue().getValue());
82+
code.addStatement("$N.getConstructorArgumentValues().addIndexedArgumentValue($L, $L)",
83+
BeanRegistrationCodeFragments.BEAN_DEFINITION_VARIABLE, entry.getKey(), renderedValue);
84+
}
85+
86+
return code.build();
87+
}
88+
89+
private static CodeBlock render(@Nullable Object value) {
90+
91+
if (value instanceof RuntimeBeanReference runtimeBeanReference
92+
&& runtimeBeanReference.getBeanType() != null) {
93+
return CodeBlock.of("new $T($T.class)", RuntimeBeanReference.class, runtimeBeanReference.getBeanType());
94+
}
95+
96+
if (value instanceof BeanReference beanReference) {
97+
return CodeBlock.of("new $T($S)", RuntimeBeanReference.class, beanReference.getBeanName());
98+
}
99+
100+
if (value instanceof String) {
101+
return CodeBlock.of("$S", value.toString());
102+
}
103+
104+
if (value == null || ClassUtils.isPrimitiveOrWrapper(value.getClass())) {
105+
return CodeBlock.of("$L", value != null ? value.toString() : "null");
106+
}
107+
108+
if (value instanceof NoOpPropertyTransformer) {
109+
return CodeBlock.of("$T.$N()", PropertyTransformers.class, "noop");
110+
}
111+
112+
if (value instanceof KeyPrefixPropertyTransformer kpt) {
113+
return CodeBlock.of("$T.$N($S)", PropertyTransformers.class, "propertyNamePrefix",
114+
kpt.getPropertyNamePrefix());
115+
}
116+
117+
if (value instanceof RequestedSecret rs) {
118+
return CodeBlock.of("$T.$N($S)", RequestedSecret.class,
119+
rs.getMode() == Mode.ROTATE ? "rotating" : "renewable", rs.getPath());
120+
121+
}
122+
123+
throw new IllegalArgumentException("Unsupported value type: " + value.getClass());
124+
}
125+
126+
}
127+
128+
}

spring-vault-core/src/main/java/org/springframework/vault/annotation/VaultPropertySourceRegistrar.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanD
135135

136136
AbstractBeanDefinition beanDefinition = createBeanDefinition(ref, renewal, propertyTransformer,
137137
ignoreSecretNotFound, potentiallyResolveRequiredPlaceholders(propertyPath));
138+
beanDefinition.setSource(annotationMetadata.getClassName());
138139

139140
do {
140141
String beanName = "vaultPropertySource#" + counter;
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.vault.aot;
17+
18+
import java.io.IOException;
19+
import java.util.stream.Stream;
20+
21+
import org.springframework.aot.hint.MemberCategory;
22+
import org.springframework.aot.hint.ReflectionHints;
23+
import org.springframework.aot.hint.RuntimeHints;
24+
import org.springframework.aot.hint.RuntimeHintsRegistrar;
25+
import org.springframework.aot.hint.TypeReference;
26+
import org.springframework.core.io.Resource;
27+
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
28+
import org.springframework.core.io.support.ResourcePatternResolver;
29+
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
30+
import org.springframework.core.type.classreading.MetadataReader;
31+
import org.springframework.util.ClassUtils;
32+
33+
/**
34+
* Runtime hints for Spring Vault.
35+
*
36+
* @author Mark Paluch
37+
* @since 3.0.1
38+
*/
39+
class VaultRuntimeHints implements RuntimeHintsRegistrar {
40+
41+
private final CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
42+
43+
@Override
44+
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
45+
46+
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(classLoader);
47+
48+
ReflectionHints reflection = hints.reflection();
49+
MemberCategory[] dataObjectCategories = new MemberCategory[] { MemberCategory.DECLARED_FIELDS,
50+
MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
51+
MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS, MemberCategory.INTROSPECT_DECLARED_METHODS };
52+
try {
53+
Resource[] resources = resolver.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
54+
+ ClassUtils.convertClassNameToResourcePath("org.springframework.vault.support") + "/*.class");
55+
56+
for (Resource resource : resources) {
57+
58+
MetadataReader metadataReader = factory.getMetadataReader(resource);
59+
String className = metadataReader.getClassMetadata().getClassName();
60+
61+
if (className.contains("-")) {
62+
continue;
63+
}
64+
65+
reflection.registerType(TypeReference.of(className), dataObjectCategories);
66+
}
67+
}
68+
catch (IOException e) {
69+
throw new RuntimeException(e);
70+
}
71+
72+
Stream.of("org.springframework.vault.core.VaultSysTemplate$GetMounts$VaultMountsResponse",
73+
"org.springframework.vault.core.VaultVersionedKeyValueTemplate$VersionedResponse",
74+
"org.springframework.vault.core.ReactiveVaultTemplate$VaultListResponse",
75+
"org.springframework.vault.core.VaultListResponse",
76+
77+
"org.springframework.vault.core.VaultTransitTemplate$RawTransitKeyImpl",
78+
"org.springframework.vault.core.VaultTransitTemplate$VaultTransitKeyImpl",
79+
80+
"org.springframework.vault.core.VaultSysTemplate$GetMounts",
81+
"org.springframework.vault.core.VaultSysTemplate$GetUnsealStatus",
82+
"org.springframework.vault.core.VaultSysTemplate$Health",
83+
"org.springframework.vault.core.VaultSysTemplate$Seal",
84+
"org.springframework.vault.core.VaultSysTemplate$VaultHealthImpl",
85+
"org.springframework.vault.core.VaultSysTemplate$VaultInitializationResponseImpl",
86+
"org.springframework.vault.core.VaultSysTemplate$VaultUnsealStatusImpl",
87+
88+
"org.springframework.vault.core.VaultVersionedKeyValueTemplate$VersionedResponse")
89+
.forEach(cls -> reflection.registerType(TypeReference.of(cls), dataObjectCategories));
90+
91+
reflection.registerTypeIfPresent(classLoader, "com.google.api.client.json.jackson2.JacksonFactory",
92+
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
93+
94+
reflection.registerTypeIfPresent(classLoader, "com.google.api.client.json.gson.GsonFactory",
95+
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
96+
97+
}
98+
99+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* Ahead-of-Time support.
3+
*/
4+
@org.springframework.lang.NonNullApi
5+
@org.springframework.lang.NonNullFields
6+
package org.springframework.vault.aot;

spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpConnectorFactory.java

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -65,28 +65,14 @@
6565
*/
6666
public class ClientHttpConnectorFactory {
6767

68-
private static final boolean REACTOR_NETTY_PRESENT = isPresent("reactor.netty.http.client.HttpClient");
68+
private static final boolean REACTOR_NETTY_PRESENT = ClassUtils.isPresent("reactor.netty.http.client.HttpClient",
69+
ClientHttpConnectorFactory.class.getClassLoader());
6970

70-
private static final boolean HTTP_COMPONENTS_PRESENT = isPresent("org.apache.hc.client5.http.impl.async");
71+
private static final boolean HTTP_COMPONENTS_PRESENT = ClassUtils.isPresent("org.apache.hc.client5.http.impl.async",
72+
ClientHttpConnectorFactory.class.getClassLoader());
7173

72-
private static final boolean JETTY_PRESENT = isPresent("org.eclipse.jetty.client.HttpClient");
73-
74-
/**
75-
* Checks for presence of all {@code classNames} using this class' classloader.
76-
* @param classNames
77-
* @return {@literal true} if all classes are present; {@literal false} if at least
78-
* one class cannot be found.
79-
*/
80-
private static boolean isPresent(String... classNames) {
81-
82-
for (String className : classNames) {
83-
if (!ClassUtils.isPresent(className, ClientHttpConnectorFactory.class.getClassLoader())) {
84-
return false;
85-
}
86-
}
87-
88-
return true;
89-
}
74+
private static final boolean JETTY_PRESENT = ClassUtils.isPresent("org.eclipse.jetty.client.HttpClient",
75+
ClientHttpConnectorFactory.class.getClassLoader());
9076

9177
/**
9278
* Create a {@link ClientHttpConnector} for the given {@link ClientOptions} and

spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpRequestFactoryFactory.java

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -89,27 +89,12 @@ public class ClientHttpRequestFactoryFactory {
8989
@SuppressWarnings("FieldMayBeFinal") // allow setting via reflection.
9090
private static Log logger = LogFactory.getLog(ClientHttpRequestFactoryFactory.class);
9191

92-
private static final boolean HTTP_COMPONENTS_PRESENT = isPresent(
93-
"org.apache.hc.client5.http.impl.classic.HttpClientBuilder");
92+
private static final boolean HTTP_COMPONENTS_PRESENT = ClassUtils.isPresent(
93+
"org.apache.hc.client5.http.impl.classic.HttpClientBuilder",
94+
ClientHttpRequestFactoryFactory.class.getClassLoader());
9495

95-
private static final boolean OKHTTP3_PRESENT = isPresent("okhttp3.OkHttpClient");
96-
97-
/**
98-
* Checks for presence of all {@code classNames} using this class' classloader.
99-
* @param classNames
100-
* @return {@literal true} if all classes are present; {@literal false} if at least
101-
* one class cannot be found.
102-
*/
103-
private static boolean isPresent(String... classNames) {
104-
105-
for (String className : classNames) {
106-
if (!ClassUtils.isPresent(className, ClientHttpRequestFactoryFactory.class.getClassLoader())) {
107-
return false;
108-
}
109-
}
110-
111-
return true;
112-
}
96+
private static final boolean OKHTTP3_PRESENT = ClassUtils.isPresent("okhttp3.OkHttpClient",
97+
ClientHttpRequestFactoryFactory.class.getClassLoader());
11398

11499
/**
115100
* Create a {@link ClientHttpRequestFactory} for the given {@link ClientOptions} and

spring-vault-core/src/main/java/org/springframework/vault/core/util/PropertyTransformers.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public static PropertyTransformer propertyNamePrefix(String propertyNamePrefix)
5757
* {@link PropertyTransformer} that passes the given properties through without
5858
* returning changed properties.
5959
*/
60-
static class NoOpPropertyTransformer implements PropertyTransformer {
60+
public static class NoOpPropertyTransformer implements PropertyTransformer {
6161

6262
static NoOpPropertyTransformer INSTANCE = new NoOpPropertyTransformer();
6363

@@ -118,7 +118,7 @@ public Map<String, Object> transformProperties(Map<String, ? extends Object> inp
118118
/**
119119
* {@link PropertyTransformer} that adds a prefix to each key name.
120120
*/
121-
static class KeyPrefixPropertyTransformer implements PropertyTransformer {
121+
public static class KeyPrefixPropertyTransformer implements PropertyTransformer {
122122

123123
private final String propertyNamePrefix;
124124

@@ -129,6 +129,10 @@ private KeyPrefixPropertyTransformer(String propertyNamePrefix) {
129129
this.propertyNamePrefix = propertyNamePrefix;
130130
}
131131

132+
public String getPropertyNamePrefix() {
133+
return propertyNamePrefix;
134+
}
135+
132136
/**
133137
* Create a new {@link KeyPrefixPropertyTransformer} that adds a prefix to each
134138
* key name.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
org.springframework.aot.hint.RuntimeHintsRegistrar=org.springframework.vault.aot.VaultRuntimeHints
2+
3+
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=org.springframework.vault.annotation.PropertySourceAotProcessor

0 commit comments

Comments
 (0)