org.assertj
assertj-core
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/annotation/VaultPropertySource.java b/spring-vault-core/src/main/java/org/springframework/vault/annotation/VaultPropertySource.java
new file mode 100644
index 00000000..caa6106a
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/annotation/VaultPropertySource.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2016 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.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+
+/**
+ * Annotation providing a convenient and declarative mechanism for adding a {@link VaultPropertySource} to Spring's
+ * {@link org.springframework.core.env.Environment Environment}. To be used in conjunction with @{@link Configuration}
+ * classes.
+ * Example usage
+ *
+ * Given a Vault path {@code secret/my-application} containing the configuration data pair
+ * {@code database.password=mysecretpassword}, the following {@code @Configuration} class uses
+ * {@code @VaultPropertySource} to contribute {@code secret/my-application} to the {@code Environment}'s set of
+ * {@code PropertySources}.
+ *
+ *
+ * @Configuration
+ * @VaultPropertySource("secret/my-application")
+ * public class AppConfig {
+ * @Autowired Environment env;
+ *
+ * @Bean
+ * public TestBean testBean() {
+ * TestBean testBean = new TestBean();
+ * testBean.setPassword(env.getProperty("database.password"));
+ * return testBean;
+ * }
+ * }
+ *
+ *
+ * Notice that the {@code Environment} object is @{@link org.springframework.beans.factory.annotation.Autowired
+ * Autowired} into the configuration class and then used when populating the {@code TestBean} object. Given the
+ * configuration above, a call to {@code testBean.getPassword()} will return "mysecretpassword".
+ *
+ * In certain situations, it may not be possible or practical to tightly control property source ordering when using
+ * {@code @VaultPropertySource} annotations. For example, if the {@code @Configuration} classes above were registered
+ * via component-scanning, the ordering is difficult to predict. In such cases - and if overriding is important - it is
+ * recommended that the user fall back to using the programmatic PropertySource API. See
+ * {@link org.springframework.core.env.ConfigurableEnvironment ConfigurableEnvironment} and
+ * {@link org.springframework.core.env.MutablePropertySources MutablePropertySources} javadocs for details.
+ *
+ * @author Mark Paluch
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Repeatable(VaultPropertySources.class)
+@Import(VaultPropertySourceRegistrar.class)
+public @interface VaultPropertySource {
+
+ /**
+ * Indicate the Vault path(s) of the properties to be retrieved. For example, {@code "secret/myapp"} or
+ * {@code "secret/my-application/profile"}.
+ *
+ * Each location will be added to the enclosing {@code Environment} as its own property source, and in the order
+ * declared.
+ */
+ String[] value();
+
+ /**
+ * Configures the name of the {@link org.springframework.vault.core.VaultTemplate} bean to be used with the property
+ * sources.
+ */
+ String vaultTemplateRef() default "vaultTemplate";
+}
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
new file mode 100644
index 00000000..b25955ab
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/annotation/VaultPropertySourceRegistrar.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2016 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.Collections;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.MutablePropertySources;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * Registrar to register {@link org.springframework.vault.core.env.VaultPropertySource}s based on
+ * {@link VaultPropertySource}.
+ *
+ * This class registers potentially multiple property sources based on different Vault paths.
+ * {@link org.springframework.vault.core.env.VaultPropertySource}s are resolved and added to
+ * {@link ConfigurableEnvironment} once the bean factory is post-processed. This allows injection of Vault properties
+ * and and lookup using the {@link org.springframework.core.env.Environment}.
+ *
+ * @author Mark Paluch
+ */
+class VaultPropertySourceRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryPostProcessor {
+
+ @Override
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
+
+ ConfigurableEnvironment env = beanFactory.getBean(ConfigurableEnvironment.class);
+ Map beans = beanFactory
+ .getBeansOfType(org.springframework.vault.core.env.VaultPropertySource.class);
+
+ MutablePropertySources propertySources = env.getPropertySources();
+
+ for (org.springframework.vault.core.env.VaultPropertySource vaultPropertySource : beans.values()) {
+
+ if (propertySources.contains(vaultPropertySource.getName())) {
+ continue;
+ }
+
+ propertySources.addLast(vaultPropertySource);
+ }
+ }
+
+ @Override
+ public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
+
+ Assert.notNull(annotationMetadata, "AnnotationMetadata must not be null!");
+ Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
+
+ registry.registerBeanDefinition("VaultPropertySourceRegistrar",
+ BeanDefinitionBuilder //
+ .rootBeanDefinition(VaultPropertySourceRegistrar.class) //
+ .setRole(BeanDefinition.ROLE_INFRASTRUCTURE) //
+ .getBeanDefinition());
+
+ Set propertySources = attributesForRepeatable(annotationMetadata,
+ VaultPropertySources.class.getName(), VaultPropertySource.class.getName());
+
+ int counter = 0;
+
+ for (AnnotationAttributes propertySource : propertySources) {
+
+ String[] paths = propertySource.getStringArray("value");
+ Assert.isTrue(paths.length > 0, "At least one @VaultPropertySource(value) location is required");
+
+ String ref = propertySource.getString("vaultTemplateRef");
+
+ Assert.hasText(ref, "'vaultTemplateRef' in @EnableVaultPropertySource must not be empty");
+
+ for (String propertyPath : paths) {
+
+ if (!StringUtils.hasText(propertyPath)) {
+ continue;
+ }
+
+ BeanDefinitionBuilder builder = BeanDefinitionBuilder
+ .rootBeanDefinition(org.springframework.vault.core.env.VaultPropertySource.class);
+
+ builder.addConstructorArgValue(propertyPath);
+ builder.addConstructorArgReference(ref);
+ builder.addConstructorArgValue(propertyPath);
+ builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+
+ registry.registerBeanDefinition("vaultPropertySource#" + counter, builder.getBeanDefinition());
+
+ counter++;
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ static Set attributesForRepeatable(AnnotationMetadata metadata, String containerClassName,
+ String annotationClassName) {
+
+ Set result = new LinkedHashSet();
+ addAttributesIfNotNull(result, metadata.getAnnotationAttributes(annotationClassName, false));
+
+ Map container = metadata.getAnnotationAttributes(containerClassName, false);
+ if (container != null && container.containsKey("value")) {
+ for (Map containedAttributes : (Map[]) container.get("value")) {
+ addAttributesIfNotNull(result, containedAttributes);
+ }
+ }
+ return Collections.unmodifiableSet(result);
+ }
+
+ private static void addAttributesIfNotNull(Set result, Map attributes) {
+ if (attributes != null) {
+ result.add(AnnotationAttributes.fromMap(attributes));
+ }
+ }
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/annotation/VaultPropertySources.java b/spring-vault-core/src/main/java/org/springframework/vault/annotation/VaultPropertySources.java
new file mode 100644
index 00000000..618f545f
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/annotation/VaultPropertySources.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016 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.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.context.annotation.Import;
+
+/**
+ * Container annotation that aggregates several {@link VaultPropertySource} annotations.
+ *
+ * Can be used natively, declaring several nested {@link VaultPropertySource} annotations. Can also be used in
+ * conjunction with Java 8's support for repeatable annotations, where {@link VaultPropertySource} can simply
+ * be declared several times on the same {@linkplain ElementType#TYPE type}, implicitly generating this container
+ * annotation.
+ *
+ * @author Mark Paluch
+ * @see VaultPropertySource
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Import(VaultPropertySourceRegistrar.class)
+public @interface VaultPropertySources {
+
+ VaultPropertySource[] value();
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/annotation/package-info.java b/spring-vault-core/src/main/java/org/springframework/vault/annotation/package-info.java
new file mode 100644
index 00000000..9255910c
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/annotation/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Annotation support for the Spring Vault.
+ */
+package org.springframework.vault.annotation;
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/env/VaultPropertySource.java b/spring-vault-core/src/main/java/org/springframework/vault/core/env/VaultPropertySource.java
new file mode 100644
index 00000000..61f9ac57
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/core/env/VaultPropertySource.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2016 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.core.env;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.env.EnumerablePropertySource;
+import org.springframework.core.env.PropertySource;
+import org.springframework.util.Assert;
+import org.springframework.vault.client.VaultException;
+import org.springframework.vault.core.VaultTemplate;
+import org.springframework.vault.support.VaultResponse;
+
+/**
+ * {@link PropertySource} that reads keys and values from a {@link VaultTemplate} and {@code path}.
+ *
+ * @author Mark Paluch
+ * @since 3.1
+ * @see org.springframework.core.env.PropertiesPropertySource
+ */
+public class VaultPropertySource extends EnumerablePropertySource {
+
+ protected final static Logger logger = LoggerFactory.getLogger(VaultPropertySource.class);
+
+ private final String path;
+ private final Map properties = new LinkedHashMap();
+ private final Object lock = new Object();
+
+ /**
+ * Create a new {@link VaultPropertySource} given a {@link VaultTemplate} and {@code path} inside of Vault. This
+ * property source loads properties upon construction.
+ *
+ * @param template must not be {@literal null}.
+ * @param path the path inside Vault (e.g. {@code secret/myapp/myproperties}. Must not be empty or {@literal null}.
+ */
+ public VaultPropertySource(VaultTemplate template, String path) {
+ this(path, template, path);
+ }
+
+ /**
+ * Create a new {@link VaultPropertySource} given a {@code name}, {@link VaultTemplate} and {@code path} inside of
+ * Vault. This property source loads properties upon construction.
+ *
+ * @param name name of the property source, must not be {@literal null}.
+ * @param template must not be {@literal null}.
+ * @param path the path inside Vault (e.g. {@code secret/myapp/myproperties}. Must not be empty or {@literal null}.
+ */
+ public VaultPropertySource(String name, VaultTemplate template, String path) {
+
+ super(name, template);
+
+ Assert.hasText(path, "Path name must contain at least one character");
+ Assert.isTrue(!path.startsWith("/"), "Path name must not start with a slash (/)");
+
+ this.path = path;
+
+ loadProperties();
+ }
+
+ /**
+ * Initialize property source and read properties from Vault.
+ */
+ protected void loadProperties() {
+
+ synchronized (lock) {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Fetching properties from Vault at %s", path));
+ }
+
+ Map properties = doGetProperties(path);
+
+ if (properties != null) {
+ this.properties.putAll(properties);
+ }
+ }
+ }
+
+ /**
+ * Hook method to obtain properties from Vault.
+ *
+ * @param path the path, must not be empty or {@literal null}.
+ * @return the resulting {@link Map} or {@literal null} if properties were not found.
+ * @throws VaultException on problems retrieving properties
+ */
+ protected Map doGetProperties(String path) throws VaultException {
+
+ VaultResponse vaultResponse = this.source.read(path);
+
+ if (vaultResponse == null || vaultResponse.getData() == null) {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("No properties found at %s", path));
+ }
+
+ return null;
+ }
+
+ return toStringMap(vaultResponse.getData());
+ }
+
+ /**
+ * Utility method converting a {@code String/Object} map to a {@code String/String} map.
+ *
+ * @param data the map
+ * @return
+ */
+ protected Map toStringMap(Map data) {
+
+ Map result = new LinkedHashMap();
+
+ if (data != null) {
+ for (String s : data.keySet()) {
+ Object value = data.get(s);
+ if (value != null) {
+ result.put(s, value.toString());
+ }
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public Object getProperty(String name) {
+ return this.properties.get(name);
+ }
+
+ @Override
+ public String[] getPropertyNames() {
+ Set strings = this.properties.keySet();
+ return strings.toArray(new String[strings.size()]);
+ }
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/env/package-info.java b/spring-vault-core/src/main/java/org/springframework/vault/core/env/package-info.java
new file mode 100644
index 00000000..9db7356e
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/core/env/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Spring Vault's environment abstraction consisting property source support.
+ */
+package org.springframework.vault.core.env;
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/annotation/VaultPropertySourceInBeanConfigurationIntegrationTest.java b/spring-vault-core/src/test/java/org/springframework/vault/annotation/VaultPropertySourceInBeanConfigurationIntegrationTest.java
new file mode 100644
index 00000000..265eaca4
--- /dev/null
+++ b/spring-vault-core/src/test/java/org/springframework/vault/annotation/VaultPropertySourceInBeanConfigurationIntegrationTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2016 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 static org.assertj.core.api.Assertions.*;
+
+import java.util.Collections;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.vault.core.VaultIntegrationTestConfiguration;
+import org.springframework.vault.core.VaultOperations;
+import org.springframework.vault.util.VaultRule;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * Integration test for {@link VaultPropertySource}.
+ *
+ * @author Mark Paluch
+ */
+@RunWith(SpringRunner.class)
+@ContextConfiguration
+public class VaultPropertySourceInBeanConfigurationIntegrationTest {
+
+ @VaultPropertySource({ "secret/myapp" })
+ static class Config extends VaultIntegrationTestConfiguration {
+
+ @Bean
+ ClientClass clientClass(@Value("${myapp}") String myapp) {
+ return new ClientClass(myapp);
+ }
+ }
+
+ @Autowired ClientClass clientClass;
+
+ @BeforeClass
+ public static void beforeClass() {
+
+ VaultRule rule = new VaultRule();
+ rule.before();
+
+ VaultOperations vaultOperations = rule.prepare().getVaultOperations();
+
+ vaultOperations.write("secret/myapp", Collections.singletonMap("myapp", "myvalue"));
+ }
+
+ @Test
+ public void clientClassShouldContainResolvedProperty() {
+ assertThat(clientClass.getMyapp()).isEqualTo("myvalue");
+ }
+
+ @Data
+ @AllArgsConstructor
+ static class ClientClass {
+ String myapp;
+ }
+}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/annotation/VaultPropertySourceIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/annotation/VaultPropertySourceIntegrationTests.java
new file mode 100644
index 00000000..782d44ba
--- /dev/null
+++ b/spring-vault-core/src/test/java/org/springframework/vault/annotation/VaultPropertySourceIntegrationTests.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2016 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 static org.assertj.core.api.Assertions.*;
+
+import java.util.Collections;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.env.Environment;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.vault.core.VaultIntegrationTestConfiguration;
+import org.springframework.vault.core.VaultOperations;
+import org.springframework.vault.util.VaultRule;
+
+/**
+ * Integration test for {@link VaultPropertySource}.
+ *
+ * @author Mark Paluch
+ */
+@RunWith(SpringRunner.class)
+@ContextConfiguration
+public class VaultPropertySourceIntegrationTests {
+
+ @VaultPropertySource({ "secret/myapp", "secret/myapp/profile" })
+ static class Config extends VaultIntegrationTestConfiguration {}
+
+ @Autowired Environment env;
+ @Autowired ApplicationContext context;
+ @Value("${myapp}") String myapp;
+
+ @BeforeClass
+ public static void beforeClass() {
+
+ VaultRule rule = new VaultRule();
+ rule.before();
+
+ VaultOperations vaultOperations = rule.prepare().getVaultOperations();
+
+ vaultOperations.write("secret/myapp", Collections.singletonMap("myapp", "myvalue"));
+ vaultOperations.write("secret/myapp/profile", Collections.singletonMap("myprofile", "myprofilevalue"));
+ }
+
+ @Test
+ public void environmentShouldResolveProperties() {
+
+ assertThat(env.getProperty("myapp")).isEqualTo("myvalue");
+ assertThat(env.getProperty("myprofile")).isEqualTo("myprofilevalue");
+ }
+
+ @Test
+ public void valueShouldInjectProperty() {
+ assertThat(myapp).isEqualTo("myvalue");
+ }
+}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/annotation/VaultPropertySourceMultipleIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/annotation/VaultPropertySourceMultipleIntegrationTests.java
new file mode 100644
index 00000000..4cc74e29
--- /dev/null
+++ b/spring-vault-core/src/test/java/org/springframework/vault/annotation/VaultPropertySourceMultipleIntegrationTests.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2016 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 static org.assertj.core.api.Assertions.*;
+
+import java.util.Collections;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.env.Environment;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.vault.core.VaultIntegrationTestConfiguration;
+import org.springframework.vault.core.VaultOperations;
+import org.springframework.vault.util.VaultRule;
+
+/**
+ * Integration test for {@link VaultPropertySource} using multiple annotations.
+ *
+ * @author Mark Paluch
+ */
+@RunWith(SpringRunner.class)
+@ContextConfiguration
+public class VaultPropertySourceMultipleIntegrationTests {
+
+ @VaultPropertySources({ @VaultPropertySource("secret/myapp/profile"), @VaultPropertySource("secret/myapp") })
+ static class Config extends VaultIntegrationTestConfiguration {}
+
+ @Autowired Environment env;
+ @Autowired ApplicationContext context;
+ @Value("${myapp}") String myapp;
+
+ @BeforeClass
+ public static void beforeClass() {
+
+ VaultRule rule = new VaultRule();
+ rule.before();
+
+ VaultOperations vaultOperations = rule.prepare().getVaultOperations();
+
+ vaultOperations.write("secret/myapp", Collections.singletonMap("myapp", "myvalue"));
+ vaultOperations.write("secret/myapp/profile", Collections.singletonMap("myprofile", "myprofilevalue"));
+ }
+
+ @Test
+ public void environmentShouldResolveProperties() {
+
+ assertThat(env.getProperty("myapp")).isEqualTo("myvalue");
+ assertThat(env.getProperty("myprofile")).isEqualTo("myprofilevalue");
+ }
+
+ @Test
+ public void valueShouldInjectProperty() {
+ assertThat(myapp).isEqualTo("myvalue");
+ }
+}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/core/VaultIntegrationTestConfiguration.java b/spring-vault-core/src/test/java/org/springframework/vault/core/VaultIntegrationTestConfiguration.java
index cedebf85..2869d9b6 100644
--- a/spring-vault-core/src/test/java/org/springframework/vault/core/VaultIntegrationTestConfiguration.java
+++ b/spring-vault-core/src/test/java/org/springframework/vault/core/VaultIntegrationTestConfiguration.java
@@ -29,7 +29,7 @@ import org.springframework.vault.util.Settings;
* @author Mark Paluch
*/
@Configuration
-class VaultIntegrationTestConfiguration extends AbstractVaultConfiguration {
+public class VaultIntegrationTestConfiguration extends AbstractVaultConfiguration {
@Override
public VaultEndpoint vaultEndpoint() {
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/core/env/VaultPropertySourceUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/core/env/VaultPropertySourceUnitTests.java
new file mode 100644
index 00000000..0db01bfc
--- /dev/null
+++ b/spring-vault-core/src/test/java/org/springframework/vault/core/env/VaultPropertySourceUnitTests.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2016 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.core.env;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.vault.core.VaultTemplate;
+import org.springframework.vault.support.VaultResponse;
+
+/**
+ * Unit tests for {@link VaultPropertySource}.
+ *
+ * @author Mark Paluch
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class VaultPropertySourceUnitTests {
+
+ @Mock VaultTemplate vaultTemplate;
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldRejectEmptyPath() throws Exception {
+ new VaultPropertySource("hello", vaultTemplate, "");
+
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldRejectPathStartingWithSlash() throws Exception {
+ new VaultPropertySource("hello", vaultTemplate, "/secret");
+ }
+
+ @Test
+ public void shouldLoadProperties() throws Exception {
+
+ prepareResponse();
+
+ VaultPropertySource vaultPropertySource = new VaultPropertySource("hello", vaultTemplate, "secret/myapp");
+
+ assertThat(vaultPropertySource.getProperty("key")).isEqualTo("value");
+ assertThat(vaultPropertySource.getProperty("integer")).isEqualTo("1");
+ }
+
+ @Test
+ public void getPropertyNamesShouldReturnNames() throws Exception {
+
+ prepareResponse();
+
+ VaultPropertySource vaultPropertySource = new VaultPropertySource("hello", vaultTemplate, "secret/myapp");
+
+ assertThat(vaultPropertySource.getPropertyNames()).contains("key", "integer");
+ }
+
+ private void prepareResponse() {
+
+ Map data = new LinkedHashMap();
+ data.put("key", "value");
+ data.put("integer", 1);
+
+ VaultResponse vaultResponse = new VaultResponse();
+ vaultResponse.setData(data);
+
+ when(vaultTemplate.read("secret/myapp")).thenReturn(vaultResponse);
+ }
+}
diff --git a/src/main/asciidoc/reference/getting-started.adoc b/src/main/asciidoc/reference/getting-started.adoc
index 8b50f4fb..709fd4a5 100644
--- a/src/main/asciidoc/reference/getting-started.adoc
+++ b/src/main/asciidoc/reference/getting-started.adoc
@@ -253,7 +253,6 @@ SslConfiguration.forTrustStore(new FileSystemResource("keystore.jks"), <2>
SslConfiguration.forKeyStore(new FileSystemResource("keystore.jks"), <3>
"changeit")
-
----
<1> Full configuration.
<2> Configuring only trust store settings.
@@ -264,6 +263,58 @@ Please note that providing `SslConfiguration` can be only
applied when either Apache Http Components or the OkHttp client
is on your class-path.
+[[vault.core.propertysupport]]
+== Vault Property Source Support
+
+Vault can be used in many different ways. One specific use-case is using Vault to store encrypted properties. Spring Vault supports Vault as property source to obtain configuration properties using Spring's http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/beans.html#beans-property-source-abstraction[PropertySource abstraction].
+
+=== Registering `VaultPropertySource`
+
+Spring Vault provides a `VaultPropertySource` to be used with Vault to obtain properties. It uses the nested `data` element to expose properties stored and encrypted in Vault.
+
+====
+[source,java]
+----
+ConfigurableApplicationContext ctx = new GenericApplicationContext();
+MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
+sources.addFirst(new VaultPropertySource(vaultTemplate, "secret/my-application"));
+----
+====
+
+In the code above, `VaultPropertySource` has been added with highest precedence in the search. If it contains a ´foo` property, it will be detected and returned ahead of any `foo` property in any other `PropertySource`. The `MutablePropertySources` API exposes a number of methods that allow for precise manipulation of the set of property sources.
+
+
+=== @VaultPropertySource
+
+The `@VaultPropertySource` annotation provides a convenient and declarative mechanism for adding a `PropertySource` to Spring’s `Environment`.
+
+To be used in conjunction with @Configuration classes.
+Example usage
+
+Given a Vault path `secret/my-application` containing the configuration data pair `database.password=mysecretpassword`, the following `@Configuration` class uses `@VaultPropertySource` to contribute `secret/my-application` to the `Environment`'s set of `PropertySources`.
+
+====
+[source,java]
+----
+@Configuration
+@VaultPropertySource("secret/my-application")
+public class AppConfig {
+
+ @Autowired Environment env;
+
+ @Bean
+ public TestBean testBean() {
+ TestBean testBean = new TestBean();
+ testBean.setPassword(env.getProperty("database.password"));
+ return testBean;
+ }
+}
+----
+====
+
+In certain situations, it may not be possible or practical to tightly control property source ordering when using `@VaultPropertySource` annotations. For example, if the @Configuration classes above were registered via component-scanning, the ordering is difficult to predict. In such cases - and if overriding is important - it is recommended that the user fall back to using the programmatic PropertySource API. See ConfigurableEnvironment and MutablePropertySources javadocs for details.
+
+
[[vault.core.executioncallback]]
== Execution callbacks