Vault repository support.
We now support Spring Data Repositories via Spring Data's KeyValue module. Domain objects can be mapped to JSON using a custom converter and created, update, deleted and queried using the repository abstraction. Vault repositories support query derivation limited to predicates on the Id property with paging and sorting.
@Configuration
@EnableVaultRepositories
public class ApplicationConfig {
@Bean
public VaultTemplate vaultTemplate() {
return new VaultTemplate(…);
}
}
@Test
public void loadAndSave() {
Credentials heisenberg = new Credentials();
heisenberg.setId("heisenberg");
heisenberg.setPassword("327215");
vaultRepository.save(heisenberg);
Iterable<Credentials> all = vaultRepository.findAll();
//
}
interface CredentialsRepository extends PagingAndSortingRepository<Credentials, String> {
}
@Data
public class Credentials {
@Id String id;
String password;
}
GET https://localhost:8200/v1/secret/credentials/heisenberg
HTTP/1.1 200 OK
Content-Type: application/json
{
// …
"renewable": false,
"lease_duration": …,
"data": {
"_class": "com.example.Credentials",
"password": "327215"
},
// …
}
See gh-128.
This commit is contained in:
71
pom.xml
71
pom.xml
@@ -15,17 +15,20 @@
|
||||
<modules>
|
||||
<module>spring-vault-dependencies</module>
|
||||
<module>spring-vault-core</module>
|
||||
<module>spring-vault-repository</module>
|
||||
<module>spring-vault-distribution</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<spring.version>5.0.0.RC3</spring.version>
|
||||
<spring-data-releasetrain.version>Kay-RC2</spring-data-releasetrain.version>
|
||||
<reactor.version>Bismuth-M3</reactor.version>
|
||||
<java.version>1.8</java.version>
|
||||
<project.type>multi</project.type>
|
||||
<dist.id>spring-vault</dist.id>
|
||||
<project.root>${basedir}</project.root>
|
||||
<shared.resources>${project.build.directory}/shared-resources</shared.resources>
|
||||
</properties>
|
||||
|
||||
<inceptionYear>2016</inceptionYear>
|
||||
@@ -116,6 +119,15 @@
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Data -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-releasetrain</artifactId>
|
||||
<version>${spring-data-releasetrain.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Testing -->
|
||||
|
||||
<dependency>
|
||||
@@ -231,6 +243,12 @@
|
||||
<version>3.5.1</version>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.0.2</version>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
@@ -247,6 +265,26 @@
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.10.4</version>
|
||||
<configuration>
|
||||
<breakiterator>true</breakiterator>
|
||||
<header>${project.name}</header>
|
||||
<source>${java.version}</source>
|
||||
<quiet>true</quiet>
|
||||
<javadocDirectory>${shared.resources}/javadoc</javadocDirectory>
|
||||
<overview>${shared.resources}/javadoc/overview.html</overview>
|
||||
<stylesheetfile>
|
||||
${shared.resources}/javadoc/spring-javadoc.css
|
||||
</stylesheetfile>
|
||||
<!-- copies doc-files subdirectory which contains image resources -->
|
||||
<docfilessubdirs>true</docfilessubdirs>
|
||||
<additionalparam>-Xdoclint:none</additionalparam>
|
||||
<links>
|
||||
<link>https://docs.spring.io/spring-data/commons/docs/current/api</link>
|
||||
<link>https://docs.spring.io/spring-data/keyvalue/docs/current/api</link>
|
||||
<link>http://docs.spring.io/spring/docs/current/javadoc-api</link>
|
||||
<link>http://docs.oracle.com/javase/8/docs/api</link>
|
||||
</links>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
@@ -471,8 +509,6 @@
|
||||
<id>distribute</id>
|
||||
|
||||
<properties>
|
||||
<shared.resources>${project.build.directory}/shared-resources
|
||||
</shared.resources>
|
||||
<maven.install.skip>true</maven.install.skip>
|
||||
<skipTests>true</skipTests>
|
||||
</properties>
|
||||
@@ -612,37 +648,6 @@
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
|
||||
<!--
|
||||
JavaDoc
|
||||
-->
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<configuration>
|
||||
<breakiterator>true</breakiterator>
|
||||
<header>${project.name}</header>
|
||||
<source>${java.version}</source>
|
||||
<quiet>true</quiet>
|
||||
<javadocDirectory>${shared.resources}/javadoc
|
||||
</javadocDirectory>
|
||||
<overview>${shared.resources}/javadoc/overview.html
|
||||
</overview>
|
||||
<stylesheetfile>
|
||||
${shared.resources}/javadoc/spring-javadoc.css
|
||||
</stylesheetfile>
|
||||
<!-- copies doc-files subdirectory which contains image resources -->
|
||||
<docfilessubdirs>true</docfilessubdirs>
|
||||
<additionalparam>-Xdoclint:none</additionalparam>
|
||||
<links>
|
||||
<link>
|
||||
http://docs.spring.io/spring/docs/current/javadoc-api/
|
||||
</link>
|
||||
<link>http://docs.oracle.com/javase/6/docs/api</link>
|
||||
</links>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!--
|
||||
Asciidoctor
|
||||
-->
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
@@ -20,6 +22,20 @@
|
||||
<targetPath>META-INF</targetPath>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>test-jar</id>
|
||||
<goals>
|
||||
<goal>test-jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -53,7 +53,7 @@ public class AwsEc2Authentication implements ClientAuthentication,
|
||||
|
||||
private static final Log logger = LogFactory.getLog(AwsEc2Authentication.class);
|
||||
|
||||
private final static char[] EMPTY = new char[0];
|
||||
private static final char[] EMPTY = new char[0];
|
||||
|
||||
private final AwsEc2AuthenticationOptions options;
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ import org.springframework.vault.support.VaultToken;
|
||||
*/
|
||||
public class CachingVaultTokenSupplier implements VaultTokenSupplier {
|
||||
|
||||
private final static Mono<VaultToken> EMPTY = Mono.empty();
|
||||
private static final Mono<VaultToken> EMPTY = Mono.empty();
|
||||
|
||||
private final VaultTokenSupplier clientAuthentication;
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class SslConfiguration {
|
||||
|
||||
private final static String DEFAULT_KEYSTORE_TYPE = KeyStore.getDefaultType();
|
||||
private static final String DEFAULT_KEYSTORE_TYPE = KeyStore.getDefaultType();
|
||||
|
||||
private final KeyStoreConfiguration keyStoreConfiguration;
|
||||
|
||||
|
||||
136
spring-vault-repository/pom.xml
Normal file
136
spring-vault-repository/pom.xml
Normal file
@@ -0,0 +1,136 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.vault</groupId>
|
||||
<artifactId>spring-vault-parent</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>spring-vault-repository</artifactId>
|
||||
<name>Spring Vault Repository</name>
|
||||
<description>Spring Vault Repository support</description>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>../src/main/resources</directory>
|
||||
<targetPath>META-INF</targetPath>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.vault</groupId>
|
||||
<artifactId>spring-vault-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.vault</groupId>
|
||||
<artifactId>spring-vault-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-beans</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-commons</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-keyvalue</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpcore</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.16.10</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Testing -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.jayway.jsonpath</groupId>
|
||||
<artifactId>json-path</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Logging -->
|
||||
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.2.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.configuration;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.ComponentScan.Filter;
|
||||
import org.springframework.data.keyvalue.core.KeyValueOperations;
|
||||
import org.springframework.data.keyvalue.repository.config.QueryCreatorType;
|
||||
import org.springframework.data.repository.config.DefaultRepositoryBaseClass;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
|
||||
import org.springframework.vault.repository.query.VaultQueryCreator;
|
||||
import org.springframework.vault.repository.support.VaultRepositoryFactoryBean;
|
||||
|
||||
/**
|
||||
* Annotation to activate Vault repositories. If no base package is configured through
|
||||
* either {@link #value()}, {@link #basePackages()} or {@link #basePackageClasses()} it
|
||||
* will trigger scanning of the package of annotated class.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@Import(VaultRepositoriesRegistrar.class)
|
||||
@QueryCreatorType(VaultQueryCreator.class)
|
||||
public @interface EnableVaultRepositories {
|
||||
|
||||
/**
|
||||
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
|
||||
* declarations e.g.: {@code @EnableVaultRepositories("org.my.pkg")} instead of
|
||||
* {@code @EnableVaultRepositories(basePackages="org.my.pkg")}.
|
||||
*/
|
||||
String[] value() default {};
|
||||
|
||||
/**
|
||||
* Base packages to scan for annotated components. {@link #value()} is an alias for
|
||||
* (and mutually exclusive with) this attribute. Use {@link #basePackageClasses()} for
|
||||
* a type-safe alternative to String-based package names.
|
||||
*/
|
||||
String[] basePackages() default {};
|
||||
|
||||
/**
|
||||
* Type-safe alternative to {@link #basePackages()} for specifying the packages to
|
||||
* scan for annotated components. The package of each class specified will be scanned.
|
||||
* Consider creating a special no-op marker class or interface in each package that
|
||||
* serves no purpose other than being referenced by this attribute.
|
||||
*/
|
||||
Class<?>[] basePackageClasses() default {};
|
||||
|
||||
/**
|
||||
* Specifies which types are not eligible for component scanning.
|
||||
*/
|
||||
Filter[] excludeFilters() default {};
|
||||
|
||||
/**
|
||||
* Specifies which types are eligible for component scanning. Further narrows the set
|
||||
* of candidate components from everything in {@link #basePackages()} to everything in
|
||||
* the base packages that matches the given filter or filters.
|
||||
*/
|
||||
Filter[] includeFilters() default {};
|
||||
|
||||
/**
|
||||
* Returns the postfix to be used when looking up custom repository implementations.
|
||||
* Defaults to {@literal Impl}. So for a repository named {@code PersonRepository} the
|
||||
* corresponding implementation class will be looked up scanning for
|
||||
* {@code PersonRepositoryImpl}.
|
||||
*/
|
||||
String repositoryImplementationPostfix() default "Impl";
|
||||
|
||||
/**
|
||||
* Configures the location of where to find the Spring Data named queries properties
|
||||
* file.
|
||||
*/
|
||||
String namedQueriesLocation() default "";
|
||||
|
||||
/**
|
||||
* Returns the key of the {@link QueryLookupStrategy} to be used for lookup queries
|
||||
* for query methods. Defaults to {@link Key#CREATE_IF_NOT_FOUND}.
|
||||
*/
|
||||
Key queryLookupStrategy() default Key.CREATE_IF_NOT_FOUND;
|
||||
|
||||
/**
|
||||
* Returns the {@link FactoryBean} class to be used for each repository instance.
|
||||
* Defaults to {@link VaultRepositoryFactoryBean}.
|
||||
*/
|
||||
Class<?> repositoryFactoryBeanClass() default VaultRepositoryFactoryBean.class;
|
||||
|
||||
/**
|
||||
* Configure the repository base class to be used to create repository proxies for
|
||||
* this particular configuration.
|
||||
*/
|
||||
Class<?> repositoryBaseClass() default DefaultRepositoryBaseClass.class;
|
||||
|
||||
/**
|
||||
* Configures the name of the {@link KeyValueOperations} bean to be used with the
|
||||
* repositories detected.
|
||||
*/
|
||||
String keyValueTemplateRef() default "vaultKeyValueTemplate";
|
||||
|
||||
/**
|
||||
* Configures whether nested repository-interfaces (e.g. defined as inner classes)
|
||||
* should be discovered by the repositories infrastructure.
|
||||
*/
|
||||
boolean considerNestedRepositories() default false;
|
||||
|
||||
/**
|
||||
* Configures the bean name of the
|
||||
* {@link org.springframework.vault.core.VaultOperations} to be used. Defaulted to
|
||||
* {@literal vaultTemplate}.
|
||||
*/
|
||||
String vaultTemplateRef() default "vaultTemplate";
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.configuration;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
|
||||
|
||||
/**
|
||||
* Vault specific {@link ImportBeanDefinitionRegistrar}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
public class VaultRepositoriesRegistrar extends RepositoryBeanDefinitionRegistrarSupport {
|
||||
|
||||
@Override
|
||||
protected Class<? extends Annotation> getAnnotation() {
|
||||
return EnableVaultRepositories.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RepositoryConfigurationExtension getExtension() {
|
||||
return new VaultRepositoryConfigurationExtension();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.configuration;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.data.keyvalue.repository.config.KeyValueRepositoryConfigurationExtension;
|
||||
import org.springframework.data.keyvalue.repository.config.QueryCreatorType;
|
||||
import org.springframework.data.keyvalue.repository.query.KeyValuePartTreeQuery;
|
||||
import org.springframework.data.keyvalue.repository.query.SpelQueryCreator;
|
||||
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationSource;
|
||||
import org.springframework.vault.repository.core.VaultKeyValueAdapter;
|
||||
import org.springframework.vault.repository.core.VaultKeyValueTemplate;
|
||||
import org.springframework.vault.repository.mapping.Secret;
|
||||
import org.springframework.vault.repository.mapping.VaultMappingContext;
|
||||
|
||||
/**
|
||||
* {@link RepositoryConfigurationExtension} for Vault.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
public class VaultRepositoryConfigurationExtension extends
|
||||
KeyValueRepositoryConfigurationExtension {
|
||||
|
||||
private static final String VAULT_ADAPTER_BEAN_NAME = "vaultKeyValueAdapter";
|
||||
|
||||
private static final String VAULT_MAPPING_CONTEXT_BEAN_NAME = "vaultMappingContext";
|
||||
|
||||
@Override
|
||||
public String getModuleName() {
|
||||
return "Vault";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getModulePrefix() {
|
||||
return "vault";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDefaultKeyValueTemplateRef() {
|
||||
return "vaultKeyValueTemplate";
|
||||
}
|
||||
|
||||
// TODO: Remove this as soon as KeyValue provides overrides for the mapping context
|
||||
// name.
|
||||
@Override
|
||||
public void postProcess(BeanDefinitionBuilder builder,
|
||||
AnnotationRepositoryConfigurationSource config) {
|
||||
|
||||
AnnotationAttributes attributes = config.getAttributes();
|
||||
|
||||
builder.addPropertyReference("keyValueOperations",
|
||||
attributes.getString(KEY_VALUE_TEMPLATE_BEAN_REF_ATTRIBUTE));
|
||||
builder.addPropertyValue("queryCreator", getQueryCreatorType(config));
|
||||
builder.addPropertyValue("queryType", getQueryType(config));
|
||||
builder.addPropertyReference("mappingContext", VAULT_MAPPING_CONTEXT_BEAN_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the query creator type to be used for the factory to set. Will lookup a
|
||||
* {@link QueryCreatorType} annotation on the {@code @Enable}-annotation or use
|
||||
* {@link SpelQueryCreator} if not found.
|
||||
*
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
private static Class<?> getQueryCreatorType(
|
||||
AnnotationRepositoryConfigurationSource config) {
|
||||
|
||||
AnnotationMetadata metadata = config.getEnableAnnotationMetadata();
|
||||
|
||||
Map<String, Object> queryCreatorAnnotationAttributes = metadata
|
||||
.getAnnotationAttributes(QueryCreatorType.class.getName());
|
||||
|
||||
if (queryCreatorAnnotationAttributes == null) {
|
||||
return SpelQueryCreator.class;
|
||||
}
|
||||
|
||||
AnnotationAttributes queryCreatorAttributes = new AnnotationAttributes(
|
||||
queryCreatorAnnotationAttributes);
|
||||
return queryCreatorAttributes.getClass("value");
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the query creator type to be used for the factory to set. Will lookup a
|
||||
* {@link QueryCreatorType} annotation on the {@code @Enable}-annotation or use
|
||||
* {@link SpelQueryCreator} if not found.
|
||||
*
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
private static Class<?> getQueryType(AnnotationRepositoryConfigurationSource config) {
|
||||
|
||||
AnnotationMetadata metadata = config.getEnableAnnotationMetadata();
|
||||
|
||||
Map<String, Object> queryCreatorAnnotationAttributes = metadata
|
||||
.getAnnotationAttributes(QueryCreatorType.class.getName());
|
||||
|
||||
if (queryCreatorAnnotationAttributes == null) {
|
||||
return KeyValuePartTreeQuery.class;
|
||||
}
|
||||
|
||||
AnnotationAttributes queryCreatorAttributes = new AnnotationAttributes(
|
||||
queryCreatorAnnotationAttributes);
|
||||
return queryCreatorAttributes.getClass("repositoryQueryType");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerBeansForRoot(BeanDefinitionRegistry registry,
|
||||
RepositoryConfigurationSource configurationSource) {
|
||||
|
||||
Optional<String> vaultTemplateRef = configurationSource
|
||||
.getAttribute("vaultTemplateRef");
|
||||
|
||||
RootBeanDefinition mappingContextDefinition = createVaultMappingContext(configurationSource);
|
||||
mappingContextDefinition.setSource(configurationSource.getSource());
|
||||
|
||||
registerIfNotAlreadyRegistered(mappingContextDefinition, registry,
|
||||
VAULT_MAPPING_CONTEXT_BEAN_NAME, configurationSource);
|
||||
|
||||
// register Adapter
|
||||
RootBeanDefinition vaultKeyValueAdapterDefinition = new RootBeanDefinition(
|
||||
VaultKeyValueAdapter.class);
|
||||
|
||||
ConstructorArgumentValues constructorArgumentValuesForVaultKeyValueAdapter = new ConstructorArgumentValues();
|
||||
|
||||
constructorArgumentValuesForVaultKeyValueAdapter.addIndexedArgumentValue(0,
|
||||
new RuntimeBeanReference(vaultTemplateRef.orElse("vaultTemplate")));
|
||||
|
||||
vaultKeyValueAdapterDefinition
|
||||
.setConstructorArgumentValues(constructorArgumentValuesForVaultKeyValueAdapter);
|
||||
|
||||
registerIfNotAlreadyRegistered(vaultKeyValueAdapterDefinition, registry,
|
||||
VAULT_ADAPTER_BEAN_NAME, configurationSource);
|
||||
|
||||
Optional<String> keyValueTemplateName = configurationSource
|
||||
.getAttribute(KEY_VALUE_TEMPLATE_BEAN_REF_ATTRIBUTE);
|
||||
|
||||
// No custom template reference configured and no matching bean definition found
|
||||
if (keyValueTemplateName.isPresent()
|
||||
&& getDefaultKeyValueTemplateRef().equals(keyValueTemplateName.get())
|
||||
&& !registry.containsBeanDefinition(keyValueTemplateName.get())) {
|
||||
|
||||
AbstractBeanDefinition beanDefinition = getDefaultKeyValueTemplateBeanDefinition(configurationSource);
|
||||
|
||||
if (beanDefinition != null) {
|
||||
registerIfNotAlreadyRegistered(beanDefinition, registry,
|
||||
keyValueTemplateName.get(), configurationSource.getSource());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RootBeanDefinition createVaultMappingContext(
|
||||
RepositoryConfigurationSource configurationSource) {
|
||||
|
||||
ConstructorArgumentValues mappingContextArgs = new ConstructorArgumentValues();
|
||||
|
||||
RootBeanDefinition mappingContextBeanDef = new RootBeanDefinition(
|
||||
VaultMappingContext.class);
|
||||
mappingContextBeanDef.setConstructorArgumentValues(mappingContextArgs);
|
||||
|
||||
return mappingContextBeanDef;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractBeanDefinition getDefaultKeyValueTemplateBeanDefinition(
|
||||
RepositoryConfigurationSource configurationSource) {
|
||||
|
||||
RootBeanDefinition keyValueTemplateDefinition = new RootBeanDefinition(
|
||||
VaultKeyValueTemplate.class);
|
||||
|
||||
ConstructorArgumentValues constructorArgumentValuesForKeyValueTemplate = new ConstructorArgumentValues();
|
||||
constructorArgumentValuesForKeyValueTemplate.addIndexedArgumentValue(0,
|
||||
new RuntimeBeanReference(VAULT_ADAPTER_BEAN_NAME));
|
||||
|
||||
constructorArgumentValuesForKeyValueTemplate.addIndexedArgumentValue(1,
|
||||
new RuntimeBeanReference(VAULT_MAPPING_CONTEXT_BEAN_NAME));
|
||||
|
||||
keyValueTemplateDefinition
|
||||
.setConstructorArgumentValues(constructorArgumentValuesForKeyValueTemplate);
|
||||
|
||||
return keyValueTemplateDefinition;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<Class<? extends Annotation>> getIdentifyingAnnotations() {
|
||||
return Collections.<Class<? extends Annotation>> singleton(Secret.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.convert;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.convert.EntityInstantiators;
|
||||
|
||||
/**
|
||||
* Base class for {@link VaultConverter} implementations. Sets up a
|
||||
* {@link GenericConversionService} and populates basic converters.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
public abstract class AbstractVaultConverter implements VaultConverter, InitializingBean {
|
||||
|
||||
protected final GenericConversionService conversionService;
|
||||
|
||||
protected CustomConversions conversions = new VaultCustomConversions();
|
||||
|
||||
protected EntityInstantiators instantiators = new EntityInstantiators();
|
||||
|
||||
/**
|
||||
* Creates a new {@link AbstractVaultConverter} using the given
|
||||
* {@link GenericConversionService}.
|
||||
*
|
||||
* @param conversionService must not be {@literal null}.
|
||||
*/
|
||||
public AbstractVaultConverter(GenericConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the given custom conversions with the converter.
|
||||
*
|
||||
* @param conversions
|
||||
*/
|
||||
public void setCustomConversions(CustomConversions conversions) {
|
||||
this.conversions = conversions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers {@link EntityInstantiators} to customize entity instantiation.
|
||||
*
|
||||
* @param instantiators
|
||||
*/
|
||||
public void setInstantiators(EntityInstantiators instantiators) {
|
||||
this.instantiators = instantiators;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConversionService getConversionService() {
|
||||
return conversionService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
initializeConverters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional converters that will be available when using the
|
||||
* {@link ConversionService} directly (e.g. for id conversion).
|
||||
*/
|
||||
private void initializeConverters() {
|
||||
conversions.registerConvertersIn(conversionService);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.convert;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.convert.DefaultTypeMapper;
|
||||
import org.springframework.data.convert.SimpleTypeInformationMapper;
|
||||
import org.springframework.data.convert.TypeAliasAccessor;
|
||||
import org.springframework.data.convert.TypeInformationMapper;
|
||||
import org.springframework.data.mapping.Alias;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link VaultTypeMapper} allowing configuration of the key to
|
||||
* lookup and store type information in {@link SecretDocument}. The key defaults to
|
||||
* {@link #DEFAULT_TYPE_KEY}. Actual type-to-{@link String} conversion and back is done in
|
||||
* {@link #readType(Object)} or {@link #getDefaultedTypeToBeUsed(Object)}. respectively.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
public class DefaultVaultTypeMapper extends DefaultTypeMapper<Map<String, Object>>
|
||||
implements VaultTypeMapper {
|
||||
|
||||
public static final String DEFAULT_TYPE_KEY = "_class";
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static final TypeInformation<Map> MAP_TYPE_INFO = ClassTypeInformation
|
||||
.from(Map.class);
|
||||
|
||||
private final @Nullable String typeKey;
|
||||
|
||||
/**
|
||||
* Creates a default {@link VaultTypeMapper} that exchanges types using the type key
|
||||
* {@literal _class}.
|
||||
*/
|
||||
public DefaultVaultTypeMapper() {
|
||||
this(DEFAULT_TYPE_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a default {@link VaultTypeMapper} that exchanges types using the given
|
||||
* {@code typeKey}.
|
||||
*
|
||||
* @param typeKey may not be {@literal null} to disable type hinting.
|
||||
*/
|
||||
public DefaultVaultTypeMapper(@Nullable String typeKey) {
|
||||
this(typeKey, Collections.singletonList(new SimpleTypeInformationMapper()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a default {@link VaultTypeMapper} that exchanges types using the given
|
||||
* {@code typeKey} and {@link MappingContext}.
|
||||
*
|
||||
* @param typeKey may not be {@literal null} to disable type hinting.
|
||||
* @param mappingContext must not be {@literal null} or empty.
|
||||
*/
|
||||
public DefaultVaultTypeMapper(@Nullable String typeKey,
|
||||
MappingContext<? extends PersistentEntity<?, ?>, ?> mappingContext) {
|
||||
this(typeKey, new SecretDocumentTypeAliasAccessor(typeKey), mappingContext,
|
||||
Collections.singletonList(new SimpleTypeInformationMapper()));
|
||||
}
|
||||
|
||||
public DefaultVaultTypeMapper(@Nullable String typeKey,
|
||||
List<? extends TypeInformationMapper> mappers) {
|
||||
this(typeKey, new SecretDocumentTypeAliasAccessor(typeKey), null, mappers);
|
||||
}
|
||||
|
||||
private DefaultVaultTypeMapper(@Nullable String typeKey,
|
||||
TypeAliasAccessor<Map<String, Object>> accessor,
|
||||
MappingContext<? extends PersistentEntity<?, ?>, ?> mappingContext,
|
||||
List<? extends TypeInformationMapper> mappers) {
|
||||
|
||||
super(accessor, mappingContext, mappers);
|
||||
|
||||
this.typeKey = typeKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given key name matches the {@literal typeKey}.
|
||||
*
|
||||
* @param key
|
||||
* @return {@literal true} if {@code key} matches the {@literal typeKey}.
|
||||
*/
|
||||
public boolean isTypeKey(String key) {
|
||||
return typeKey != null && typeKey.equals(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TypeInformation<?> getFallbackTypeFor(Map<String, Object> source) {
|
||||
return MAP_TYPE_INFO;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link TypeAliasAccessor} to store aliases in a {@link SecretDocument}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
static class SecretDocumentTypeAliasAccessor implements
|
||||
TypeAliasAccessor<Map<String, Object>> {
|
||||
|
||||
private final @Nullable String typeKey;
|
||||
|
||||
SecretDocumentTypeAliasAccessor(@Nullable String typeKey) {
|
||||
this.typeKey = typeKey;
|
||||
}
|
||||
|
||||
public Alias readAliasFrom(Map<String, Object> source) {
|
||||
return typeKey == null ? Alias.NONE : Alias.ofNullable(source.get(typeKey));
|
||||
}
|
||||
|
||||
public void writeTypeTo(Map<String, Object> sink, Object alias) {
|
||||
if (typeKey != null) {
|
||||
sink.put(typeKey, alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,757 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.convert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.core.CollectionFactory;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.convert.EntityInstantiator;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.PreferredConstructor.Parameter;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.data.mapping.model.ParameterValueProvider;
|
||||
import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
|
||||
import org.springframework.data.mapping.model.PropertyValueProvider;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.vault.repository.mapping.VaultPersistentEntity;
|
||||
import org.springframework.vault.repository.mapping.VaultPersistentProperty;
|
||||
|
||||
/**
|
||||
* {@link VaultConverter} that uses a {@link MappingContext} to do sophisticated mapping
|
||||
* of domain objects to {@link SecretDocument}. This converter converts between Map-typed
|
||||
* representations and domain objects without use of a JSON library.
|
||||
* {@link SecretDocument} is the input to JSON mapping to exchange secrets with Vault.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
public class MappingVaultConverter extends AbstractVaultConverter {
|
||||
|
||||
private final MappingContext<? extends VaultPersistentEntity<?>, VaultPersistentProperty> mappingContext;
|
||||
|
||||
private VaultTypeMapper typeMapper;
|
||||
|
||||
public MappingVaultConverter(
|
||||
MappingContext<? extends VaultPersistentEntity<?>, VaultPersistentProperty> mappingContext) {
|
||||
|
||||
super(new DefaultConversionService());
|
||||
|
||||
Assert.notNull(mappingContext, "MappingContext must not be null");
|
||||
|
||||
this.mappingContext = mappingContext;
|
||||
this.typeMapper = new DefaultVaultTypeMapper(
|
||||
DefaultVaultTypeMapper.DEFAULT_TYPE_KEY, mappingContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link VaultTypeMapper} to be used to add type information to
|
||||
* {@link SecretDocument}s created by the converter and how to lookup type information
|
||||
* from {@link SecretDocument}s when reading them. Uses a
|
||||
* {@link DefaultVaultTypeMapper} by default. Setting this to {@literal null} will
|
||||
* reset the {@link org.springframework.data.convert.TypeMapper} to the default one.
|
||||
*
|
||||
* @param typeMapper the typeMapper to set, must not be {@literal null}.
|
||||
*/
|
||||
public void setTypeMapper(VaultTypeMapper typeMapper) {
|
||||
|
||||
Assert.notNull(typeMapper, "VaultTypeMapper must not be null");
|
||||
this.typeMapper = typeMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MappingContext<? extends VaultPersistentEntity<?>, VaultPersistentProperty> getMappingContext() {
|
||||
return mappingContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> S read(Class<S> type, SecretDocument source) {
|
||||
return read(ClassTypeInformation.from(type), source);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <S> S read(TypeInformation<S> type, Object source) {
|
||||
|
||||
SecretDocument secretDocument = getSecretDocument(source);
|
||||
|
||||
TypeInformation<? extends S> typeToUse = secretDocument != null ? typeMapper
|
||||
.readType(secretDocument.getBody(), type)
|
||||
: (TypeInformation) ClassTypeInformation.OBJECT;
|
||||
Class<? extends S> rawType = typeToUse.getType();
|
||||
|
||||
if (conversions.hasCustomReadTarget(source.getClass(), rawType)) {
|
||||
return conversionService.convert(source, rawType);
|
||||
}
|
||||
|
||||
if (SecretDocument.class.isAssignableFrom(rawType)) {
|
||||
return (S) source;
|
||||
}
|
||||
|
||||
if (Map.class.isAssignableFrom(rawType) && secretDocument != null) {
|
||||
return (S) secretDocument.getBody();
|
||||
}
|
||||
|
||||
if (typeToUse.isMap() && secretDocument != null) {
|
||||
return (S) readMap(typeToUse, secretDocument.getBody());
|
||||
}
|
||||
|
||||
if (typeToUse.equals(ClassTypeInformation.OBJECT)) {
|
||||
return (S) source;
|
||||
}
|
||||
|
||||
return read(
|
||||
(VaultPersistentEntity<S>) mappingContext
|
||||
.getRequiredPersistentEntity(typeToUse),
|
||||
secretDocument);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
private SecretDocument getSecretDocument(Object source) {
|
||||
|
||||
SecretDocument secretDocument = null;
|
||||
if (source instanceof Map) {
|
||||
secretDocument = new SecretDocument((Map) source);
|
||||
}
|
||||
else if (source instanceof SecretDocument) {
|
||||
secretDocument = (SecretDocument) source;
|
||||
}
|
||||
return secretDocument;
|
||||
}
|
||||
|
||||
private ParameterValueProvider<VaultPersistentProperty> getParameterProvider(
|
||||
VaultPersistentEntity<?> entity, SecretDocument source) {
|
||||
|
||||
VaultPropertyValueProvider provider = new VaultPropertyValueProvider(source);
|
||||
|
||||
PersistentEntityParameterValueProvider<VaultPersistentProperty> parameterProvider = new PersistentEntityParameterValueProvider<>(
|
||||
entity, provider, source);
|
||||
|
||||
return new ParameterValueProvider<VaultPersistentProperty>() {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public <T> T getParameterValue(Parameter<T, VaultPersistentProperty> parameter) {
|
||||
|
||||
Object value = parameterProvider.getParameterValue(parameter);
|
||||
return value != null ? readValue(value, parameter.getType()) : null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private <S> S read(VaultPersistentEntity<S> entity, SecretDocument source) {
|
||||
|
||||
ParameterValueProvider<VaultPersistentProperty> provider = getParameterProvider(
|
||||
entity, source);
|
||||
EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity);
|
||||
S instance = instantiator.createInstance(entity, provider);
|
||||
|
||||
PersistentPropertyAccessor accessor = new ConvertingPropertyAccessor(
|
||||
entity.getPropertyAccessor(instance), conversionService);
|
||||
|
||||
VaultPersistentProperty idProperty = entity.getIdProperty();
|
||||
SecretDocumentAccessor documentAccessor = new SecretDocumentAccessor(source);
|
||||
|
||||
// make sure id property is set before all other properties
|
||||
Object idValue;
|
||||
|
||||
if (idProperty != null && documentAccessor.hasValue(idProperty)) {
|
||||
|
||||
idValue = readIdValue(idProperty, documentAccessor);
|
||||
accessor.setProperty(idProperty, idValue);
|
||||
}
|
||||
|
||||
VaultPropertyValueProvider valueProvider = new VaultPropertyValueProvider(
|
||||
documentAccessor);
|
||||
|
||||
readProperties(entity, accessor, idProperty, documentAccessor, valueProvider);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object readIdValue(VaultPersistentProperty idProperty,
|
||||
SecretDocumentAccessor documentAccessor) {
|
||||
|
||||
Object resolvedValue = documentAccessor.get(idProperty);
|
||||
|
||||
return resolvedValue != null ? readValue(resolvedValue,
|
||||
idProperty.getTypeInformation()) : null;
|
||||
}
|
||||
|
||||
private void readProperties(VaultPersistentEntity<?> entity,
|
||||
PersistentPropertyAccessor accessor,
|
||||
@Nullable VaultPersistentProperty idProperty,
|
||||
SecretDocumentAccessor documentAccessor,
|
||||
VaultPropertyValueProvider valueProvider) {
|
||||
|
||||
for (VaultPersistentProperty prop : entity) {
|
||||
|
||||
// we skip the id property since it was already set
|
||||
if (idProperty != null && idProperty.equals(prop)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entity.isConstructorArgument(prop) || !documentAccessor.hasValue(prop)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
accessor.setProperty(prop, valueProvider.getPropertyValue(prop));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T readValue(Object value, TypeInformation<?> type) {
|
||||
|
||||
Class<?> rawType = type.getType();
|
||||
|
||||
if (conversions.hasCustomReadTarget(value.getClass(), rawType)) {
|
||||
return (T) conversionService.convert(value, rawType);
|
||||
}
|
||||
else if (value instanceof List) {
|
||||
return (T) readCollectionOrArray(type, (List) value);
|
||||
}
|
||||
else if (value instanceof Map) {
|
||||
return (T) read(type, (Map) value);
|
||||
}
|
||||
else {
|
||||
return (T) getPotentiallyConvertedSimpleRead(value, rawType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the given {@link List} into a collection of the given {@link TypeInformation}
|
||||
* .
|
||||
*
|
||||
* @param targetType must not be {@literal null}.
|
||||
* @param sourceValue must not be {@literal null}.
|
||||
* @return the converted {@link Collection} or array, will never be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private Object readCollectionOrArray(TypeInformation<?> targetType, List sourceValue) {
|
||||
|
||||
Assert.notNull(targetType, "Target type must not be null!");
|
||||
|
||||
Class<?> collectionType = targetType.getType();
|
||||
|
||||
TypeInformation<?> componentType = targetType.getComponentType() != null ? targetType
|
||||
.getComponentType() : ClassTypeInformation.OBJECT;
|
||||
Class<?> rawComponentType = componentType.getType();
|
||||
|
||||
collectionType = Collection.class.isAssignableFrom(collectionType) ? collectionType
|
||||
: List.class;
|
||||
Collection<Object> items = targetType.getType().isArray() ? new ArrayList<>(
|
||||
sourceValue.size()) : CollectionFactory.createCollection(collectionType,
|
||||
rawComponentType, sourceValue.size());
|
||||
|
||||
if (sourceValue.isEmpty()) {
|
||||
return getPotentiallyConvertedSimpleRead(items, collectionType);
|
||||
}
|
||||
|
||||
for (Object obj : sourceValue) {
|
||||
|
||||
if (obj instanceof Map) {
|
||||
items.add(read(componentType, (Map) obj));
|
||||
}
|
||||
else if (obj instanceof List) {
|
||||
items.add(readCollectionOrArray(ClassTypeInformation.OBJECT, (List) obj));
|
||||
}
|
||||
else {
|
||||
items.add(getPotentiallyConvertedSimpleRead(obj, rawComponentType));
|
||||
}
|
||||
}
|
||||
|
||||
return getPotentiallyConvertedSimpleRead(items, targetType.getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the given {@link Map} into a {@link Map}. will recursively resolve nested
|
||||
* {@link Map}s as well.
|
||||
*
|
||||
* @param type the {@link Map} {@link TypeInformation} to be used to unmarshall this
|
||||
* {@link Map}.
|
||||
* @param sourceMap must not be {@literal null}
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Map<Object, Object> readMap(TypeInformation<?> type,
|
||||
Map<String, Object> sourceMap) {
|
||||
|
||||
Assert.notNull(sourceMap, "Source map must not be null!");
|
||||
|
||||
Class<?> mapType = typeMapper.readType(sourceMap, type).getType();
|
||||
|
||||
TypeInformation<?> keyType = type.getComponentType();
|
||||
TypeInformation<?> valueType = type.getMapValueType();
|
||||
|
||||
Class<?> rawKeyType = keyType != null ? keyType.getType() : null;
|
||||
Class<?> rawValueType = valueType != null ? valueType.getType() : null;
|
||||
|
||||
Map<Object, Object> map = CollectionFactory.createMap(mapType, rawKeyType,
|
||||
sourceMap.keySet().size());
|
||||
|
||||
for (Entry<String, Object> entry : sourceMap.entrySet()) {
|
||||
|
||||
if (typeMapper.isTypeKey(entry.getKey())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object key = entry.getKey();
|
||||
|
||||
if (rawKeyType != null && !rawKeyType.isAssignableFrom(key.getClass())) {
|
||||
key = conversionService.convert(key, rawKeyType);
|
||||
}
|
||||
|
||||
Object value = entry.getValue();
|
||||
TypeInformation<?> defaultedValueType = valueType != null ? valueType
|
||||
: ClassTypeInformation.OBJECT;
|
||||
|
||||
if (value instanceof Map) {
|
||||
map.put(key, read(defaultedValueType, (Map) value));
|
||||
}
|
||||
else if (value instanceof List) {
|
||||
map.put(key,
|
||||
readCollectionOrArray(valueType != null ? valueType
|
||||
: ClassTypeInformation.LIST, (List) value));
|
||||
}
|
||||
else {
|
||||
map.put(key, getPotentiallyConvertedSimpleRead(value, rawValueType));
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether we have a custom conversion for the given simple object. Converts
|
||||
* the given value if so, applies {@link Enum} handling or returns the value as is.
|
||||
*
|
||||
* @param value
|
||||
* @param target must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private Object getPotentiallyConvertedSimpleRead(@Nullable Object value,
|
||||
@Nullable Class<?> target) {
|
||||
|
||||
if (value == null || target == null || target.isAssignableFrom(value.getClass())) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (Enum.class.isAssignableFrom(target)) {
|
||||
return Enum.valueOf((Class<Enum>) target, value.toString());
|
||||
}
|
||||
|
||||
return conversionService.convert(value, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Object source, SecretDocument sink) {
|
||||
|
||||
Class<?> entityType = ClassUtils.getUserClass(source.getClass());
|
||||
TypeInformation<? extends Object> type = ClassTypeInformation.from(entityType);
|
||||
|
||||
SecretDocumentAccessor documentAccessor = new SecretDocumentAccessor(sink);
|
||||
|
||||
writeInternal(source, documentAccessor, type);
|
||||
|
||||
boolean handledByCustomConverter = conversions.hasCustomWriteTarget(entityType,
|
||||
SecretDocument.class);
|
||||
if (!handledByCustomConverter) {
|
||||
typeMapper.writeType(type, sink.getBody());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal write conversion method which should be used for nested invocations.
|
||||
*
|
||||
* @param obj
|
||||
* @param sink
|
||||
* @param typeHint
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void writeInternal(Object obj, SecretDocumentAccessor sink,
|
||||
@Nullable TypeInformation<?> typeHint) {
|
||||
|
||||
Class<?> entityType = obj.getClass();
|
||||
Optional<Class<?>> customTarget = conversions.getCustomWriteTarget(entityType,
|
||||
SecretDocument.class);
|
||||
|
||||
if (customTarget.isPresent()) {
|
||||
|
||||
SecretDocument result = conversionService.convert(obj, SecretDocument.class);
|
||||
|
||||
if (result.getId() != null) {
|
||||
sink.setId(result.getId());
|
||||
}
|
||||
|
||||
sink.getBody().putAll(result.getBody());
|
||||
return;
|
||||
}
|
||||
|
||||
if (Map.class.isAssignableFrom(entityType)) {
|
||||
writeMapInternal((Map<Object, Object>) obj, sink.getBody(),
|
||||
ClassTypeInformation.MAP);
|
||||
return;
|
||||
}
|
||||
|
||||
VaultPersistentEntity<?> entity = mappingContext
|
||||
.getRequiredPersistentEntity(entityType);
|
||||
writeInternal(obj, sink, entity);
|
||||
addCustomTypeKeyIfNecessary(typeHint, obj, sink);
|
||||
}
|
||||
|
||||
protected void writeInternal(Object obj, SecretDocumentAccessor sink,
|
||||
VaultPersistentEntity<?> entity) {
|
||||
|
||||
PersistentPropertyAccessor accessor = entity.getPropertyAccessor(obj);
|
||||
|
||||
VaultPersistentProperty idProperty = entity.getIdProperty();
|
||||
if (idProperty != null && !sink.hasValue(idProperty)) {
|
||||
|
||||
Object value = accessor.getProperty(idProperty);
|
||||
|
||||
if (value != null) {
|
||||
sink.put(idProperty, value);
|
||||
}
|
||||
}
|
||||
writeProperties(entity, accessor, sink, idProperty);
|
||||
}
|
||||
|
||||
private void writeProperties(VaultPersistentEntity<?> entity,
|
||||
PersistentPropertyAccessor accessor, SecretDocumentAccessor sink,
|
||||
@Nullable VaultPersistentProperty idProperty) {
|
||||
|
||||
// Write the properties
|
||||
for (VaultPersistentProperty prop : entity) {
|
||||
|
||||
if (prop.equals(idProperty) || !prop.isWritable()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object value = accessor.getProperty(prop);
|
||||
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!conversions.isSimpleType(value.getClass())) {
|
||||
writePropertyInternal(value, sink, prop);
|
||||
}
|
||||
else {
|
||||
|
||||
sink.put(prop, getPotentiallyConvertedSimpleWrite(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
protected void writePropertyInternal(@Nullable Object obj,
|
||||
SecretDocumentAccessor accessor, VaultPersistentProperty prop) {
|
||||
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TypeInformation<?> valueType = ClassTypeInformation.from(obj.getClass());
|
||||
TypeInformation<?> type = prop.getTypeInformation();
|
||||
|
||||
if (valueType.isCollectionLike()) {
|
||||
List<Object> collectionInternal = createCollection(asCollection(obj), prop);
|
||||
accessor.put(prop, collectionInternal);
|
||||
return;
|
||||
}
|
||||
|
||||
if (valueType.isMap()) {
|
||||
Map<String, Object> mapDbObj = createMap((Map<Object, Object>) obj, prop);
|
||||
accessor.put(prop, mapDbObj);
|
||||
return;
|
||||
}
|
||||
|
||||
// Lookup potential custom target type
|
||||
Optional<Class<?>> basicTargetType = conversions.getCustomWriteTarget(obj
|
||||
.getClass());
|
||||
|
||||
if (basicTargetType.isPresent()) {
|
||||
|
||||
accessor.put(prop, conversionService.convert(obj, basicTargetType.get()));
|
||||
return;
|
||||
}
|
||||
|
||||
VaultPersistentEntity<?> entity = isSubtype(prop.getType(), obj.getClass()) ? mappingContext
|
||||
.getRequiredPersistentEntity(obj.getClass()) : mappingContext
|
||||
.getRequiredPersistentEntity(type);
|
||||
|
||||
SecretDocumentAccessor nested = accessor.writeNested(prop);
|
||||
|
||||
writeInternal(obj, nested, entity);
|
||||
addCustomTypeKeyIfNecessary(ClassTypeInformation.from(prop.getRawType()), obj,
|
||||
nested);
|
||||
}
|
||||
|
||||
private static boolean isSubtype(Class<?> left, Class<?> right) {
|
||||
return left.isAssignableFrom(right) && !left.equals(right);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given {@link Collection} using the given {@link VaultPersistentProperty}
|
||||
* information.
|
||||
*
|
||||
* @param collection must not be {@literal null}.
|
||||
* @param property must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected List<Object> createCollection(Collection<?> collection,
|
||||
VaultPersistentProperty property) {
|
||||
|
||||
return writeCollectionInternal(collection, property.getTypeInformation(),
|
||||
new ArrayList<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the given {@link List} with values from the given {@link Collection}.
|
||||
*
|
||||
* @param source the collection to create a {@link List} for, must not be
|
||||
* {@literal null}.
|
||||
* @param type the {@link TypeInformation} to consider or {@literal null} if unknown.
|
||||
* @param sink the {@link List} to write to.
|
||||
* @return
|
||||
*/
|
||||
private List<Object> writeCollectionInternal(Collection<?> source,
|
||||
@Nullable TypeInformation<?> type, List<Object> sink) {
|
||||
|
||||
TypeInformation<?> componentType = null;
|
||||
|
||||
if (type != null) {
|
||||
componentType = type.getComponentType();
|
||||
}
|
||||
|
||||
for (Object element : source) {
|
||||
|
||||
Class<?> elementType = element == null ? null : element.getClass();
|
||||
|
||||
if (elementType == null || conversions.isSimpleType(elementType)) {
|
||||
sink.add(getPotentiallyConvertedSimpleWrite(element));
|
||||
}
|
||||
else if (element instanceof Collection || elementType.isArray()) {
|
||||
sink.add(writeCollectionInternal(asCollection(element), componentType,
|
||||
new ArrayList<>()));
|
||||
}
|
||||
else {
|
||||
SecretDocumentAccessor accessor = new SecretDocumentAccessor(
|
||||
new SecretDocument());
|
||||
writeInternal(element, accessor, componentType);
|
||||
sink.add(accessor.getBody());
|
||||
}
|
||||
}
|
||||
|
||||
return sink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given {@link Map} using the given {@link VaultPersistentProperty}
|
||||
* information.
|
||||
*
|
||||
* @param map must not {@literal null}.
|
||||
* @param property must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected Map<String, Object> createMap(Map<Object, Object> map,
|
||||
VaultPersistentProperty property) {
|
||||
|
||||
Assert.notNull(map, "Given map must not be null!");
|
||||
Assert.notNull(property, "PersistentProperty must not be null!");
|
||||
|
||||
return writeMapInternal(map, new LinkedHashMap<>(), property.getTypeInformation());
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given {@link Map} to the given {@link Map} considering the given
|
||||
* {@link TypeInformation}.
|
||||
*
|
||||
* @param obj must not be {@literal null}.
|
||||
* @param bson must not be {@literal null}.
|
||||
* @param propertyType must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected Map<String, Object> writeMapInternal(Map<Object, Object> obj,
|
||||
Map<String, Object> bson, TypeInformation<?> propertyType) {
|
||||
|
||||
for (Map.Entry<Object, Object> entry : obj.entrySet()) {
|
||||
|
||||
Object key = entry.getKey();
|
||||
Object val = entry.getValue();
|
||||
|
||||
if (conversions.isSimpleType(key.getClass())) {
|
||||
|
||||
String simpleKey = key.toString();
|
||||
if (val == null || conversions.isSimpleType(val.getClass())) {
|
||||
bson.put(simpleKey, val);
|
||||
}
|
||||
else if (val instanceof Collection || val.getClass().isArray()) {
|
||||
|
||||
bson.put(
|
||||
simpleKey,
|
||||
writeCollectionInternal(asCollection(val),
|
||||
propertyType.getMapValueType(), new ArrayList<>()));
|
||||
}
|
||||
else {
|
||||
SecretDocumentAccessor nested = new SecretDocumentAccessor(
|
||||
new SecretDocument());
|
||||
TypeInformation<?> valueTypeInfo = propertyType.isMap() ? propertyType
|
||||
.getMapValueType() : ClassTypeInformation.OBJECT;
|
||||
writeInternal(val, nested, valueTypeInfo);
|
||||
bson.put(simpleKey, nested.getBody());
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new MappingException("Cannot use a complex object as a key value.");
|
||||
}
|
||||
}
|
||||
|
||||
return bson;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds custom type information to the given {@link SecretDocument} if necessary. That
|
||||
* is if the value is not the same as the one given. This is usually the case if you
|
||||
* store a subtype of the actual declared type of the property.
|
||||
*
|
||||
* @param type
|
||||
* @param value must not be {@literal null}.
|
||||
* @param accessor must not be {@literal null}.
|
||||
*/
|
||||
protected void addCustomTypeKeyIfNecessary(@Nullable TypeInformation<?> type,
|
||||
Object value, SecretDocumentAccessor accessor) {
|
||||
|
||||
Class<?> reference = type != null ? type.getActualType().getType() : Object.class;
|
||||
Class<?> valueType = ClassUtils.getUserClass(value.getClass());
|
||||
|
||||
boolean notTheSameClass = !valueType.equals(reference);
|
||||
if (notTheSameClass) {
|
||||
typeMapper.writeType(valueType, accessor.getBody());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether we have a custom conversion registered for the given value into an
|
||||
* arbitrary simple Vault type. Returns the converted value if so. If not, we perform
|
||||
* special enum handling or simply return the value as is.
|
||||
*
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
private Object getPotentiallyConvertedSimpleWrite(@Nullable Object value) {
|
||||
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Optional<Class<?>> customTarget = conversions.getCustomWriteTarget(value
|
||||
.getClass());
|
||||
|
||||
if (customTarget.isPresent()) {
|
||||
return conversionService.convert(value, customTarget.get());
|
||||
}
|
||||
|
||||
if (ObjectUtils.isArray(value)) {
|
||||
|
||||
if (value instanceof byte[]) {
|
||||
return value;
|
||||
}
|
||||
return asCollection(value);
|
||||
}
|
||||
|
||||
return Enum.class.isAssignableFrom(value.getClass()) ? ((Enum<?>) value).name()
|
||||
: value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns given object as {@link Collection}. Will return the {@link Collection} as
|
||||
* is if the source is a {@link Collection} already, will convert an array into a
|
||||
* {@link Collection} or simply create a single element collection for everything
|
||||
* else.
|
||||
*
|
||||
* @param source
|
||||
* @return
|
||||
*/
|
||||
private static Collection<?> asCollection(Object source) {
|
||||
|
||||
if (source instanceof Collection) {
|
||||
return (Collection<?>) source;
|
||||
}
|
||||
|
||||
return source.getClass().isArray() ? CollectionUtils.arrayToList(source)
|
||||
: Collections.singleton(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link PropertyValueProvider} to evaluate a SpEL expression if present on the
|
||||
* property or simply accesses the field of the configured source
|
||||
* {@link SecretDocument}.
|
||||
*
|
||||
*/
|
||||
class VaultPropertyValueProvider implements
|
||||
PropertyValueProvider<VaultPersistentProperty> {
|
||||
|
||||
private final SecretDocumentAccessor source;
|
||||
|
||||
VaultPropertyValueProvider(SecretDocument source) {
|
||||
|
||||
Assert.notNull(source, "Source document must no be null!");
|
||||
|
||||
this.source = new SecretDocumentAccessor(source);
|
||||
}
|
||||
|
||||
VaultPropertyValueProvider(SecretDocumentAccessor accessor) {
|
||||
|
||||
Assert.notNull(accessor, "SecretDocumentAccessor must no be null!");
|
||||
|
||||
this.source = accessor;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <T> T getPropertyValue(VaultPersistentProperty property) {
|
||||
|
||||
Object value = source.get(property);
|
||||
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return readValue(value, property.getTypeInformation());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.convert;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.vault.support.VaultResponse;
|
||||
|
||||
/**
|
||||
* Vault database exchange object containing data before/after it's exchanged with Vault.
|
||||
* A {@link SecretDocument} is basically an object with an {@code id} and a body
|
||||
* represented as {@link Map} of {@link String} and {@link Object}. It can be created
|
||||
* {@link #from(String, VaultResponse) from} an Id and {@link VaultResponse}.
|
||||
* <p>
|
||||
* A secret document can hold simple properties, {@link java.util.Collection list}
|
||||
* properties and nested objects as {@link Map}s.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
public class SecretDocument {
|
||||
|
||||
private @Nullable String id;
|
||||
|
||||
private final Map<String, Object> body;
|
||||
|
||||
/**
|
||||
* Create a new, empty {@link SecretDocument}.
|
||||
*/
|
||||
public SecretDocument() {
|
||||
this(null, new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link SecretDocument} given a {@link Map body map}.
|
||||
* @param body must not be {@literal null}.
|
||||
*/
|
||||
public SecretDocument(Map<String, Object> body) {
|
||||
this(null, body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link SecretDocument} given an {@code id} and {@link Map body map}.
|
||||
* @param id may be {@literal null}.
|
||||
* @param body must not be {@literal null}.
|
||||
*/
|
||||
public SecretDocument(@Nullable String id, Map<String, Object> body) {
|
||||
|
||||
Assert.notNull(body, "Body must not be null");
|
||||
|
||||
this.id = id;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public SecretDocument(String id) {
|
||||
this(id, new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a {@link SecretDocument} from an {@code id} and
|
||||
* {@link VaultResponse}.
|
||||
*
|
||||
* @param id must not be {@literal null}.
|
||||
* @param vaultResponse must not be {@literal null}.
|
||||
* @return the {@link SecretDocument}.
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public static SecretDocument from(@Nullable String id, VaultResponse vaultResponse) {
|
||||
return new SecretDocument(id, vaultResponse.getData());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Id or {@literal null} if the Id is not set.
|
||||
*/
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Id.
|
||||
*
|
||||
* @param id may be {@literal null}.
|
||||
*/
|
||||
public void setId(@Nullable String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the body of this {@link SecretDocument}
|
||||
*/
|
||||
public Map<String, Object> getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a value from the secret document by its {@code key}.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @return the value or {@literal null}, if the value is not present.
|
||||
*/
|
||||
@Nullable
|
||||
public Object get(String key) {
|
||||
return body.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value in the secret document.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param value must not be {@literal null}.
|
||||
*/
|
||||
public void put(String key, Object value) {
|
||||
this.body.put(key, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.convert;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.vault.repository.mapping.VaultPersistentProperty;
|
||||
|
||||
/**
|
||||
* Wrapper value object for a {@link SecretDocument} to be able to access raw values by
|
||||
* {@link VaultPersistentProperty} references. The accessors will transparently resolve
|
||||
* nested document values that a {@link VaultPersistentProperty} might refer to through a
|
||||
* path expression in field names.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
class SecretDocumentAccessor {
|
||||
|
||||
private final SecretDocument document;
|
||||
|
||||
private final Map<String, Object> body;
|
||||
|
||||
/**
|
||||
* Creates a new {@link SecretDocumentAccessor} for the given {@link SecretDocument}.
|
||||
*
|
||||
* @param document must be a {@link SecretDocument} effectively, must not be
|
||||
* {@literal null}.
|
||||
*/
|
||||
SecretDocumentAccessor(SecretDocument document) {
|
||||
|
||||
Assert.notNull(document, "SecretDocument must not be null!");
|
||||
|
||||
this.document = document;
|
||||
this.body = document.getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link SecretDocumentAccessor} for the given {@link SecretDocument}
|
||||
* and {@link Map body}.
|
||||
*
|
||||
* @param document must be a {@link SecretDocument} effectively, must not be
|
||||
* {@literal null}
|
||||
* @param body must not be {@literal null}.
|
||||
*/
|
||||
private SecretDocumentAccessor(SecretDocument document, Map<String, Object> body) {
|
||||
|
||||
Assert.notNull(document, "SecretDocument must not be null!");
|
||||
Assert.notNull(body, "Body must not be null!");
|
||||
|
||||
this.document = document;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the given value into the backing {@link SecretDocument} based on the
|
||||
* coordinates defined through the given {@link VaultPersistentProperty}. By default
|
||||
* this will be the plain field name. But field names might also consist of path
|
||||
* traversals so we might need to create intermediate {@link Map}s.
|
||||
*
|
||||
* @param prop must not be {@literal null}.
|
||||
* @param value
|
||||
*/
|
||||
void put(VaultPersistentProperty prop, @Nullable Object value) {
|
||||
|
||||
Assert.notNull(prop, "VaultPersistentProperty must not be null!");
|
||||
String fieldName = prop.getName();
|
||||
|
||||
if (prop.isIdProperty()) {
|
||||
this.document.setId((String) value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fieldName.contains(".")) {
|
||||
this.body.put(fieldName, value);
|
||||
return;
|
||||
}
|
||||
|
||||
Iterator<String> parts = Arrays.asList(fieldName.split("\\.")).iterator();
|
||||
Map<String, Object> document = this.body;
|
||||
|
||||
while (parts.hasNext()) {
|
||||
|
||||
String part = parts.next();
|
||||
|
||||
if (parts.hasNext()) {
|
||||
document = getOrCreateNestedDocument(part, document);
|
||||
}
|
||||
else {
|
||||
document.put(fieldName, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value the given {@link VaultPersistentProperty} refers to. By default
|
||||
* this will be a direct field but the method will also transparently resolve nested
|
||||
* values the {@link VaultPersistentProperty} might refer to through a path expression
|
||||
* in the field name metadata.
|
||||
*
|
||||
* @param property must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
Object get(VaultPersistentProperty property) {
|
||||
|
||||
String fieldName = property.getName();
|
||||
|
||||
if (property.isIdProperty()) {
|
||||
return this.document.getId();
|
||||
}
|
||||
|
||||
if (!fieldName.contains(".")) {
|
||||
return this.body.get(fieldName);
|
||||
}
|
||||
|
||||
Iterator<String> parts = Arrays.asList(fieldName.split("\\.")).iterator();
|
||||
Map<String, Object> source = this.body;
|
||||
Object result = null;
|
||||
|
||||
while (source != null && parts.hasNext()) {
|
||||
|
||||
result = source.get(parts.next());
|
||||
|
||||
if (parts.hasNext()) {
|
||||
source = getAsMap(result);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the underlying {@link SecretDocument} has a value ({@literal null}
|
||||
* or non-{@literal null}) for the given {@link VaultPersistentProperty}.
|
||||
*
|
||||
* @param property must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
boolean hasValue(VaultPersistentProperty property) {
|
||||
|
||||
Assert.notNull(property, "Property must not be null!");
|
||||
|
||||
if (property.isIdProperty()) {
|
||||
return StringUtils.hasText(this.document.getId());
|
||||
}
|
||||
|
||||
String fieldName = property.getName();
|
||||
|
||||
if (!fieldName.contains(".")) {
|
||||
return this.body.containsKey(fieldName);
|
||||
}
|
||||
|
||||
String[] parts = fieldName.split("\\.");
|
||||
Map<String, Object> source = this.body;
|
||||
|
||||
Object result = null;
|
||||
|
||||
for (int i = 1; i < parts.length; i++) {
|
||||
|
||||
result = source.get(parts[i - 1]);
|
||||
source = getAsMap(result);
|
||||
|
||||
if (source == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return source.containsKey(parts[parts.length - 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given source object as map, i.e. maps as is or {@literal null}
|
||||
* otherwise.
|
||||
*
|
||||
* @param source can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
private static Map<String, Object> getAsMap(Object source) {
|
||||
|
||||
if (source instanceof Map) {
|
||||
return (Map<String, Object>) source;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Map} which either already exists in the given source under the
|
||||
* given key, or creates a new nested one, registers it with the source and returns
|
||||
* it.
|
||||
*
|
||||
* @param key must not be {@literal null} or empty.
|
||||
* @param source must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Map<String, Object> getOrCreateNestedDocument(String key,
|
||||
Map<String, Object> source) {
|
||||
|
||||
Object existing = source.get(key);
|
||||
|
||||
if (existing instanceof Map) {
|
||||
return (Map<String, Object>) existing;
|
||||
}
|
||||
|
||||
Map<String, Object> nested = new LinkedHashMap<>();
|
||||
source.put(key, nested);
|
||||
|
||||
return nested;
|
||||
}
|
||||
|
||||
public Map<String, Object> getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.document.setId(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a nested {@link SecretDocumentAccessor} for a
|
||||
* {@link VaultPersistentProperty}. Nested accessors allows mapping of structured
|
||||
* hierarchies and represent accessors to nested maps.
|
||||
*
|
||||
* @param property must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public SecretDocumentAccessor writeNested(VaultPersistentProperty property) {
|
||||
|
||||
Map<String, Object> body = (Map) get(property);
|
||||
|
||||
if (body == null) {
|
||||
body = new LinkedHashMap<>();
|
||||
put(property, body);
|
||||
}
|
||||
|
||||
return new SecretDocumentAccessor(document, body);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.convert;
|
||||
|
||||
import org.springframework.data.convert.EntityConverter;
|
||||
import org.springframework.vault.repository.mapping.VaultPersistentEntity;
|
||||
import org.springframework.vault.repository.mapping.VaultPersistentProperty;
|
||||
|
||||
/**
|
||||
* Central Vault-specific converter interface.
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface VaultConverter
|
||||
extends
|
||||
EntityConverter<VaultPersistentEntity<?>, VaultPersistentProperty, Object, SecretDocument> {
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.convert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.GenericConverter;
|
||||
import org.springframework.data.convert.JodaTimeConverters;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.vault.repository.mapping.VaultSimpleTypes;
|
||||
|
||||
/**
|
||||
* Value object to capture custom conversion. {@link VaultCustomConversions} also act as
|
||||
* factory for {@link org.springframework.data.mapping.model.SimpleTypeHolder}
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
* @see org.springframework.data.convert.CustomConversions
|
||||
* @see org.springframework.data.mapping.model.SimpleTypeHolder
|
||||
* @see VaultSimpleTypes
|
||||
*/
|
||||
public class VaultCustomConversions extends
|
||||
org.springframework.data.convert.CustomConversions {
|
||||
|
||||
private static final StoreConversions STORE_CONVERSIONS;
|
||||
private static final List<Object> STORE_CONVERTERS;
|
||||
|
||||
static {
|
||||
|
||||
List<Object> converters = new ArrayList<>();
|
||||
|
||||
converters.add(CustomToStringConverter.INSTANCE);
|
||||
converters.addAll(JodaTimeConverters.getConvertersToRegister());
|
||||
|
||||
STORE_CONVERTERS = Collections.unmodifiableList(converters);
|
||||
STORE_CONVERSIONS = StoreConversions
|
||||
.of(VaultSimpleTypes.HOLDER, STORE_CONVERTERS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty {@link VaultCustomConversions} object.
|
||||
*/
|
||||
VaultCustomConversions() {
|
||||
this(Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link VaultCustomConversions} instance registering the given
|
||||
* converters.
|
||||
*
|
||||
* @param converters must not be {@literal null}.
|
||||
*/
|
||||
public VaultCustomConversions(List<?> converters) {
|
||||
super(STORE_CONVERSIONS, converters);
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
private enum CustomToStringConverter implements GenericConverter {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
public Set<ConvertiblePair> getConvertibleTypes() {
|
||||
|
||||
ConvertiblePair localeToString = new ConvertiblePair(Locale.class,
|
||||
String.class);
|
||||
ConvertiblePair booleanToString = new ConvertiblePair(Character.class,
|
||||
String.class);
|
||||
|
||||
return new HashSet<>(Arrays.asList(localeToString, booleanToString));
|
||||
}
|
||||
|
||||
public Object convert(Object source, TypeDescriptor sourceType,
|
||||
TypeDescriptor targetType) {
|
||||
return source.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.convert;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.convert.TypeMapper;
|
||||
|
||||
/**
|
||||
* Vault-specific {@link TypeMapper} exposing that {@link SecretDocument}s might contain a
|
||||
* type key.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface VaultTypeMapper extends TypeMapper<Map<String, Object>> {
|
||||
|
||||
/**
|
||||
* Returns whether the given key is the type key.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean isTypeKey(String key);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Spring Vault specific converter infrastructure.
|
||||
*/
|
||||
@NonNullApi
|
||||
package org.springframework.vault.repository.convert;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.core;
|
||||
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.repository.core.support.PersistentEntityInformation;
|
||||
import org.springframework.vault.repository.mapping.VaultPersistentEntity;
|
||||
|
||||
/**
|
||||
* {@link VaultEntityInformation} implementation using a {@link VaultPersistentEntity}
|
||||
* instance to lookup the necessary information. Can be configured with a custom
|
||||
* collection to be returned which will trump the one returned by the
|
||||
* {@link VaultPersistentEntity} if given.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
public class MappingVaultEntityInformation<T, ID> extends
|
||||
PersistentEntityInformation<T, ID> implements VaultEntityInformation<T, ID> {
|
||||
|
||||
/**
|
||||
* @param entity
|
||||
*/
|
||||
public MappingVaultEntityInformation(VaultPersistentEntity<T> entity) {
|
||||
|
||||
super(entity);
|
||||
|
||||
if (!entity.hasIdProperty()) {
|
||||
|
||||
throw new MappingException(
|
||||
String.format(
|
||||
"Entity %s requires to have an explicit id field. Did you forget to provide one using @Id?",
|
||||
entity.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.core;
|
||||
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
|
||||
/**
|
||||
* Vault-specific {@link EntityInformation}.
|
||||
*
|
||||
* @param <T> Domain type.
|
||||
* @param <ID> Id type.
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface VaultEntityInformation<T, ID> extends EntityInformation<T, ID> {
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.data.keyvalue.core.AbstractKeyValueAdapter;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.util.CloseableIterator;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.vault.core.VaultOperations;
|
||||
import org.springframework.vault.repository.convert.MappingVaultConverter;
|
||||
import org.springframework.vault.repository.convert.SecretDocument;
|
||||
import org.springframework.vault.repository.convert.VaultConverter;
|
||||
import org.springframework.vault.repository.mapping.VaultMappingContext;
|
||||
import org.springframework.vault.repository.mapping.VaultPersistentEntity;
|
||||
import org.springframework.vault.repository.mapping.VaultPersistentProperty;
|
||||
import org.springframework.vault.support.VaultResponse;
|
||||
|
||||
/**
|
||||
* Vault-specific {@link org.springframework.data.keyvalue.core.KeyValueAdapter}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
public class VaultKeyValueAdapter extends AbstractKeyValueAdapter {
|
||||
|
||||
private final VaultOperations vaultOperations;
|
||||
|
||||
private final VaultConverter vaultConverter;
|
||||
|
||||
/**
|
||||
* Create a new {@link VaultKeyValueAdapter} given {@link VaultOperations}.
|
||||
*
|
||||
* @param vaultOperations must not be {@literal null}.
|
||||
*/
|
||||
public VaultKeyValueAdapter(VaultOperations vaultOperations) {
|
||||
this(vaultOperations, new MappingVaultConverter(new VaultMappingContext()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link VaultKeyValueAdapter} given {@link VaultOperations} and
|
||||
* {@link VaultConverter}.
|
||||
*
|
||||
* @param vaultOperations must not be {@literal null}.
|
||||
* @param vaultConverter must not be {@literal null}.
|
||||
*/
|
||||
public VaultKeyValueAdapter(VaultOperations vaultOperations,
|
||||
VaultConverter vaultConverter) {
|
||||
|
||||
super(VaultQueryEngine.INSTANCE);
|
||||
|
||||
Assert.notNull(vaultOperations, "VaultOperations must not be null");
|
||||
Assert.notNull(vaultConverter, "VaultConverter must not be null");
|
||||
|
||||
this.vaultOperations = vaultOperations;
|
||||
this.vaultConverter = vaultConverter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object put(Object id, Object item, String keyspace) {
|
||||
|
||||
SecretDocument secretDocument = new SecretDocument(id.toString());
|
||||
vaultConverter.write(item, secretDocument);
|
||||
|
||||
vaultOperations.write(createKey(id, keyspace), secretDocument.getBody());
|
||||
|
||||
return secretDocument;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object id, String keyspace) {
|
||||
return doList(keyspace).contains(id.toString());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object get(Object id, String keyspace) {
|
||||
return get(id, keyspace, Object.class);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public <T> T get(Object id, String keyspace, Class<T> type) {
|
||||
|
||||
VaultResponse response = vaultOperations.read(createKey(id, keyspace));
|
||||
|
||||
if (response == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SecretDocument document = SecretDocument.from(id.toString(), response);
|
||||
|
||||
return vaultConverter.read(type, document);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object delete(Object id, String keyspace) {
|
||||
return delete(id, keyspace, Object.class);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public <T> T delete(Object id, String keyspace, Class<T> type) {
|
||||
|
||||
T entity = get(id, keyspace, type);
|
||||
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
vaultOperations.delete(createKey(id, keyspace));
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<?> getAllOf(String keyspace) {
|
||||
|
||||
List<String> list = doList(keyspace);
|
||||
List<Object> items = new ArrayList<>(list.size());
|
||||
|
||||
for (String id : list) {
|
||||
items.add(get(id, keyspace));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloseableIterator<Entry<Object, Object>> entries(String keyspace) {
|
||||
|
||||
List<String> list = doList(keyspace);
|
||||
Iterator<String> iterator = list.iterator();
|
||||
|
||||
return new CloseableIterator<Entry<Object, Object>>() {
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<Object, Object> next() {
|
||||
|
||||
final String key = iterator.next();
|
||||
|
||||
return new Entry<Object, Object>() {
|
||||
@Override
|
||||
public Object getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object getValue() {
|
||||
return get(key, keyspace);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object setValue(Object value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAllOf(String keyspace) {
|
||||
|
||||
List<String> ids = doList(keyspace);
|
||||
|
||||
for (String id : ids) {
|
||||
vaultOperations.delete(createKey(id, keyspace));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count(String keyspace) {
|
||||
|
||||
List<String> list = doList(keyspace);
|
||||
|
||||
return list.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
}
|
||||
|
||||
List<String> doList(String keyspace) {
|
||||
|
||||
List<String> list = vaultOperations.list(keyspace);
|
||||
|
||||
return list == null ? Collections.emptyList() : list;
|
||||
}
|
||||
|
||||
private String createKey(Object id, String keyspace) {
|
||||
return String.format("%s/%s", keyspace, id);
|
||||
}
|
||||
|
||||
MappingContext<? extends VaultPersistentEntity<?>, VaultPersistentProperty> getMappingContext() {
|
||||
return vaultConverter.getMappingContext();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.core;
|
||||
|
||||
import org.springframework.data.keyvalue.core.KeyValueAdapter;
|
||||
import org.springframework.data.keyvalue.core.KeyValueTemplate;
|
||||
import org.springframework.vault.repository.mapping.VaultMappingContext;
|
||||
|
||||
/**
|
||||
* Vault-specific {@link KeyValueTemplate}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
public class VaultKeyValueTemplate extends KeyValueTemplate {
|
||||
|
||||
/**
|
||||
* Create a new {@link VaultKeyValueTemplate} given {@link KeyValueAdapter} and
|
||||
* {@link VaultMappingContext}.
|
||||
*
|
||||
* @param adapter must not be {@literal null}.
|
||||
*/
|
||||
public VaultKeyValueTemplate(KeyValueAdapter adapter) {
|
||||
this(adapter, new VaultMappingContext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link VaultKeyValueTemplate} given {@link KeyValueAdapter} and
|
||||
* {@link VaultMappingContext}.
|
||||
*
|
||||
* @param adapter must not be {@literal null}.
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
*/
|
||||
public VaultKeyValueTemplate(KeyValueAdapter adapter,
|
||||
VaultMappingContext mappingContext) {
|
||||
super(adapter, mappingContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
// no-op to prevent clear() call.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.domain.Sort.NullHandling;
|
||||
import org.springframework.data.domain.Sort.Order;
|
||||
import org.springframework.data.keyvalue.core.CriteriaAccessor;
|
||||
import org.springframework.data.keyvalue.core.QueryEngine;
|
||||
import org.springframework.data.keyvalue.core.SortAccessor;
|
||||
import org.springframework.data.keyvalue.core.SpelPropertyComparator;
|
||||
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.context.PersistentPropertyPath;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.vault.repository.query.VaultQuery;
|
||||
|
||||
/**
|
||||
* Query engine for Vault repository query methods. This engine queries Vault for all
|
||||
* elements in the keyspace and applies {@link java.util.function.Predicate}s to the
|
||||
* object id. Queries can contain only predicate subjects pointing to the
|
||||
* {@link org.springframework.data.annotation.Id} property.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
* @see VaultQuery
|
||||
* @see org.springframework.vault.repository.query.VaultQueryCreator
|
||||
*/
|
||||
class VaultQueryEngine extends
|
||||
QueryEngine<VaultKeyValueAdapter, VaultQuery, Comparator<?>> {
|
||||
|
||||
static final VaultQueryEngine INSTANCE = new VaultQueryEngine();
|
||||
|
||||
private VaultQueryEngine() {
|
||||
super(VaultCriteriaAccessor.INSTANCE, SpelSortAccessor.INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Collection<?> execute(VaultQuery vaultQuery, Comparator<?> comparator,
|
||||
long offset, int rows, String keyspace) {
|
||||
return execute(vaultQuery, comparator, offset, rows, keyspace, Object.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Collection<T> execute(VaultQuery vaultQuery, Comparator<?> comparator,
|
||||
long offset, int rows, String keyspace, Class<T> type) {
|
||||
|
||||
validatePropertyPaths(vaultQuery);
|
||||
|
||||
Stream<String> stream = getAdapter().doList(keyspace).stream();
|
||||
|
||||
if (vaultQuery != null) {
|
||||
stream = stream.filter(vaultQuery::test);
|
||||
}
|
||||
|
||||
if (comparator == null) {
|
||||
|
||||
if (offset > 0) {
|
||||
stream = stream.skip(offset);
|
||||
}
|
||||
|
||||
if (rows > 0) {
|
||||
stream = stream.limit(rows);
|
||||
}
|
||||
}
|
||||
|
||||
Stream<T> typed = stream.map(it -> getAdapter().get(it, keyspace, type));
|
||||
|
||||
if (comparator != null) {
|
||||
|
||||
typed = typed.sorted((Comparator) comparator);
|
||||
|
||||
if (offset > 0) {
|
||||
typed = typed.skip(offset);
|
||||
}
|
||||
|
||||
if (rows > 0) {
|
||||
typed = typed.limit(rows);
|
||||
}
|
||||
}
|
||||
|
||||
return typed.collect(Collectors.toCollection(ArrayList::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count(VaultQuery vaultQuery, String keyspace) {
|
||||
|
||||
validatePropertyPaths(vaultQuery);
|
||||
|
||||
Stream<String> stream = getAdapter().doList(keyspace).stream();
|
||||
|
||||
if (vaultQuery != null) {
|
||||
stream = stream.filter(vaultQuery::test);
|
||||
}
|
||||
|
||||
return stream.count();
|
||||
}
|
||||
|
||||
private void validatePropertyPaths(VaultQuery vaultQuery) {
|
||||
|
||||
if (vaultQuery == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Stream<PropertyPath> stream = vaultQuery.getPropertyPaths().stream();
|
||||
|
||||
stream.map(it -> getAdapter().getMappingContext().getPersistentPropertyPath(it))
|
||||
.filter(it -> it.getLeafProperty() != null)
|
||||
.map(PersistentPropertyPath::getLeafProperty)
|
||||
.filter(it -> !it.isIdProperty())
|
||||
.forEach(
|
||||
property -> {
|
||||
throw new InvalidDataAccessApiUsageException(String.format(
|
||||
"Cannot create criteria for non-@Id property %s",
|
||||
property));
|
||||
});
|
||||
}
|
||||
|
||||
enum VaultCriteriaAccessor implements CriteriaAccessor<VaultQuery> {
|
||||
|
||||
INSTANCE;
|
||||
@Override
|
||||
public VaultQuery resolve(KeyValueQuery<?> query) {
|
||||
return (VaultQuery) query.getCriteria();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link SortAccessor} implementation capable of creating
|
||||
* {@link SpelPropertyComparator}.
|
||||
*/
|
||||
enum SpelSortAccessor implements SortAccessor<Comparator<?>> {
|
||||
INSTANCE;
|
||||
|
||||
private final SpelExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
@Override
|
||||
public Comparator<?> resolve(KeyValueQuery<?> query) {
|
||||
|
||||
if (query == null || query.getSort() == null || query.getSort().isUnsorted()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Optional<Comparator<?>> comparator = Optional.empty();
|
||||
for (Order order : query.getSort()) {
|
||||
|
||||
SpelPropertyComparator<Object> spelSort = new SpelPropertyComparator<>(
|
||||
order.getProperty(), parser);
|
||||
|
||||
if (Direction.DESC.equals(order.getDirection())) {
|
||||
|
||||
spelSort.desc();
|
||||
|
||||
if (!NullHandling.NATIVE.equals(order.getNullHandling())) {
|
||||
spelSort = NullHandling.NULLS_FIRST.equals(order
|
||||
.getNullHandling()) ? spelSort.nullsFirst() : spelSort
|
||||
.nullsLast();
|
||||
}
|
||||
}
|
||||
|
||||
if (!comparator.isPresent()) {
|
||||
comparator = Optional.of(spelSort);
|
||||
}
|
||||
else {
|
||||
|
||||
SpelPropertyComparator<Object> spelSortToUse = spelSort;
|
||||
comparator = comparator.map(it -> it.thenComparing(spelSortToUse));
|
||||
}
|
||||
}
|
||||
|
||||
return comparator
|
||||
.orElseThrow(() -> new IllegalStateException(
|
||||
"No sort definitions have been added to this CompoundComparator to compare"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.mapping;
|
||||
|
||||
import org.springframework.data.keyvalue.core.mapping.BasicKeyValuePersistentEntity;
|
||||
import org.springframework.data.keyvalue.core.mapping.KeySpaceResolver;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link VaultPersistentEntity} implementation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
public class BasicVaultPersistentEntity<T> extends
|
||||
BasicKeyValuePersistentEntity<T, VaultPersistentProperty> implements
|
||||
VaultPersistentEntity<T> {
|
||||
|
||||
private final String keyspace;
|
||||
|
||||
private final String secretBackend;
|
||||
|
||||
/**
|
||||
* Creates new {@link BasicVaultPersistentEntity}.
|
||||
*
|
||||
* @param information must not be {@literal null}.
|
||||
* @param fallbackKeySpaceResolver can be {@literal null}.
|
||||
*/
|
||||
public BasicVaultPersistentEntity(TypeInformation<T> information,
|
||||
KeySpaceResolver fallbackKeySpaceResolver) {
|
||||
super(information, fallbackKeySpaceResolver);
|
||||
|
||||
Secret annotation = findAnnotation(Secret.class);
|
||||
|
||||
String keyspace = super.getKeySpace();
|
||||
String secretBackend = "secret";
|
||||
|
||||
if (annotation != null) {
|
||||
if (StringUtils.hasText(annotation.backend())) {
|
||||
secretBackend = annotation.backend();
|
||||
}
|
||||
}
|
||||
|
||||
this.secretBackend = secretBackend;
|
||||
this.keyspace = String.format("%s/%s", secretBackend, keyspace);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKeySpace() {
|
||||
return keyspace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSecretBackend() {
|
||||
return secretBackend;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.mapping;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.data.annotation.Persistent;
|
||||
import org.springframework.data.keyvalue.annotation.KeySpace;
|
||||
|
||||
/**
|
||||
* {@link Secret} marks objects as aggregate roots to be stored in Vault.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Persistent
|
||||
@Documented
|
||||
@Inherited
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(value = { ElementType.TYPE })
|
||||
@KeySpace
|
||||
public @interface Secret {
|
||||
|
||||
/**
|
||||
* The prefix to distinguish between domain types.
|
||||
* @see KeySpace
|
||||
*/
|
||||
@AliasFor(annotation = KeySpace.class, attribute = "value")
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* Secret backend mount, defaults to {@literal secret}.
|
||||
*/
|
||||
String backend() default "secret";
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.mapping;
|
||||
|
||||
import org.springframework.data.keyvalue.core.mapping.KeySpaceResolver;
|
||||
import org.springframework.data.keyvalue.core.mapping.context.KeyValueMappingContext;
|
||||
import org.springframework.data.mapping.model.Property;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Mapping context for {@link VaultPersistentEntity Vault-specific entities}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
public class VaultMappingContext extends
|
||||
KeyValueMappingContext<VaultPersistentEntity<?>, VaultPersistentProperty> {
|
||||
|
||||
private KeySpaceResolver fallbackKeySpaceResolver = SimpleClassNameKeySpaceResolver.INSTANCE;
|
||||
|
||||
public KeySpaceResolver getFallbackKeySpaceResolver() {
|
||||
return fallbackKeySpaceResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFallbackKeySpaceResolver(KeySpaceResolver fallbackKeySpaceResolver) {
|
||||
this.fallbackKeySpaceResolver = fallbackKeySpaceResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> VaultPersistentEntity<?> createPersistentEntity(
|
||||
TypeInformation<T> typeInformation) {
|
||||
return new BasicVaultPersistentEntity<>(typeInformation, fallbackKeySpaceResolver);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VaultPersistentProperty createPersistentProperty(Property property,
|
||||
VaultPersistentEntity<?> owner, SimpleTypeHolder simpleTypeHolder) {
|
||||
return new VaultPersistentProperty(property, owner, simpleTypeHolder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Most trivial implementation of {@link KeySpaceResolver} returning the
|
||||
* {@link Class#getName()}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
enum SimpleClassNameKeySpaceResolver implements KeySpaceResolver {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String resolveKeySpace(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
return StringUtils
|
||||
.uncapitalize(ClassUtils.getUserClass(type).getSimpleName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.mapping;
|
||||
|
||||
import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
|
||||
/**
|
||||
* Vault specific {@link PersistentEntity}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface VaultPersistentEntity<T> extends
|
||||
KeyValuePersistentEntity<T, VaultPersistentProperty> {
|
||||
|
||||
/**
|
||||
* @return the secret backend in which this {@link PersistentEntity} is stored.
|
||||
*/
|
||||
String getSecretBackend();
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.mapping;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.model.Property;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
|
||||
/**
|
||||
* Vault-specific {@link KeyValuePersistentProperty}. By default, if a property is named
|
||||
* {@code id} it's used as Id property.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
public class VaultPersistentProperty extends
|
||||
KeyValuePersistentProperty<VaultPersistentProperty> {
|
||||
|
||||
private static final Set<String> SUPPORTED_ID_PROPERTY_NAMES = new HashSet<String>();
|
||||
|
||||
static {
|
||||
SUPPORTED_ID_PROPERTY_NAMES.add("id");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link VaultPersistentProperty}.
|
||||
*
|
||||
* @param property must not be {@literal null}.
|
||||
* @param owner must not be {@literal null}.
|
||||
* @param simpleTypeHolder must not be {@literal null}.
|
||||
*/
|
||||
public VaultPersistentProperty(Property property,
|
||||
PersistentEntity<?, VaultPersistentProperty> owner,
|
||||
SimpleTypeHolder simpleTypeHolder) {
|
||||
|
||||
super(property, owner, simpleTypeHolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIdProperty() {
|
||||
return super.isIdProperty() || SUPPORTED_ID_PROPERTY_NAMES.contains(getName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.mapping;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.vault.repository.convert.SecretDocument;
|
||||
|
||||
/**
|
||||
* Simple constant holder for a {@link SimpleTypeHolder} enriched with Vault-specific
|
||||
* simple (JSON) types.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
public abstract class VaultSimpleTypes {
|
||||
|
||||
static {
|
||||
|
||||
Set<Class<?>> simpleTypes = new HashSet<Class<?>>();
|
||||
simpleTypes.add(SecretDocument.class);
|
||||
|
||||
VAULT_SIMPLE_TYPES = Collections.unmodifiableSet(simpleTypes);
|
||||
}
|
||||
|
||||
private static final Set<Class<?>> VAULT_SIMPLE_TYPES;
|
||||
public static final SimpleTypeHolder HOLDER = new SimpleTypeHolder(
|
||||
VAULT_SIMPLE_TYPES, true);
|
||||
|
||||
private VaultSimpleTypes() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.query;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.vault.repository.convert.SecretDocument;
|
||||
|
||||
/**
|
||||
* Vault query consisting of a single {@link Predicate}. A new (empty) query evaluates
|
||||
* unconditionally to {@literal true} and can be composed using {@link #and(VaultQuery)}
|
||||
* and {@link #or(VaultQuery)}.
|
||||
* <p />
|
||||
* A query can express predicates only against the
|
||||
* {@link org.springframework.data.annotation.Id} field of a {@link SecretDocument}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
public class VaultQuery {
|
||||
|
||||
private final Predicate<String> predicate;
|
||||
|
||||
private final Set<PropertyPath> propertyPaths;
|
||||
|
||||
/**
|
||||
* Create a new {@link VaultQuery} that evaluates unconditionally to {@literal true}.
|
||||
*/
|
||||
public VaultQuery() {
|
||||
this(s -> true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link VaultQuery} given {@link Predicate}.
|
||||
*
|
||||
* @param predicate must not be {@literal null}.
|
||||
*/
|
||||
public VaultQuery(Predicate<String> predicate) {
|
||||
this(predicate, Collections.emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link VaultQuery} given {@link Predicate} and {@link PropertyPath}.
|
||||
*
|
||||
* @param predicate must not be {@literal null}.
|
||||
*/
|
||||
public VaultQuery(Predicate<String> predicate, PropertyPath propertyPath) {
|
||||
|
||||
Assert.notNull(predicate, "Predicate must not be null");
|
||||
Assert.notNull(propertyPath, "PropertyPath must not be null");
|
||||
|
||||
this.predicate = predicate;
|
||||
this.propertyPaths = Collections.singleton(propertyPath);
|
||||
}
|
||||
|
||||
private VaultQuery(Predicate<String> predicate, Set<PropertyPath> propertyPaths) {
|
||||
|
||||
Assert.notNull(propertyPaths, "PropertyPaths must not be null");
|
||||
Assert.notNull(predicate, "Predicate must not be null");
|
||||
|
||||
this.predicate = predicate;
|
||||
this.propertyPaths = propertyPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the query against a {@link SecretDocument}.
|
||||
*
|
||||
* @param document must not be {@literal null}.
|
||||
* @return {@literal true} if the predicate matches, {@literal false} otherwise.
|
||||
*/
|
||||
public boolean test(SecretDocument document) {
|
||||
|
||||
Assert.notNull(predicate, "Predicate must not be null");
|
||||
|
||||
return predicate.test(document.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the query against a {@link String}.
|
||||
*
|
||||
* @param id must not be {@literal null}.
|
||||
* @return {@literal true} if the predicate matches, {@literal false} otherwise.
|
||||
*/
|
||||
public boolean test(String id) {
|
||||
|
||||
Assert.notNull(id, "Id to test must not be null");
|
||||
|
||||
return predicate.test(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose a new {@link VaultQuery} using predicates of {@literal this} and the
|
||||
* {@code other} query using logical {@code AND}.
|
||||
*
|
||||
* @param other must not be {@literal null}.
|
||||
* @return a new composed {@link VaultQuery}.
|
||||
* @see Predicate#and(Predicate)
|
||||
*/
|
||||
public VaultQuery and(VaultQuery other) {
|
||||
|
||||
Set<PropertyPath> propertyPaths = new HashSet<>(this.propertyPaths.size()
|
||||
+ other.propertyPaths.size(), 1);
|
||||
propertyPaths.addAll(this.propertyPaths);
|
||||
propertyPaths.addAll(other.propertyPaths);
|
||||
|
||||
return new VaultQuery(this.predicate.and(other.predicate), propertyPaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose a new {@link VaultQuery} using predicates of {@literal this} and the
|
||||
* {@code other} query using logical {@code OR}.
|
||||
*
|
||||
* @param other must not be {@literal null}.
|
||||
* @return a new composed {@link VaultQuery}.
|
||||
* @see Predicate#and(Predicate)
|
||||
*/
|
||||
public VaultQuery or(VaultQuery other) {
|
||||
|
||||
Set<PropertyPath> propertyPaths = new HashSet<>(this.propertyPaths.size()
|
||||
+ other.propertyPaths.size(), 1);
|
||||
propertyPaths.addAll(this.propertyPaths);
|
||||
propertyPaths.addAll(other.propertyPaths);
|
||||
|
||||
return new VaultQuery(this.predicate.or(other.predicate), propertyPaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose a new {@link VaultQuery} using predicates of {@literal this} query and the
|
||||
* {@code other} {@link Predicate} using logical {@code AND}.
|
||||
*
|
||||
* @param other must not be {@literal null}.
|
||||
* @return a new composed {@link VaultQuery}.
|
||||
* @see Predicate#and(Predicate)
|
||||
*/
|
||||
public VaultQuery and(Predicate<String> predicate, PropertyPath propertyPath) {
|
||||
|
||||
Set<PropertyPath> propertyPaths = new HashSet<>(this.propertyPaths.size() + 1, 1);
|
||||
propertyPaths.addAll(this.propertyPaths);
|
||||
propertyPaths.add(propertyPath);
|
||||
|
||||
return new VaultQuery(this.predicate.and(predicate), propertyPaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the underlying predicate.
|
||||
*/
|
||||
public Predicate<String> getPredicate() {
|
||||
return predicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return constrained {@link PropertyPath}s for this {@link VaultQuery}.
|
||||
*/
|
||||
public Set<PropertyPath> getPropertyPaths() {
|
||||
return Collections.unmodifiableSet(propertyPaths);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.query;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
|
||||
import org.springframework.data.repository.query.parser.Part;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
import org.springframework.data.repository.query.parser.Part.IgnoreCaseType;
|
||||
import org.springframework.data.repository.query.parser.Part.Type;
|
||||
|
||||
/**
|
||||
* Query creator for Vault queries. Vault queries are limited to criterias constraining
|
||||
* the {@link org.springframework.data.annotation.Id} property. A query consists of
|
||||
* chained {@link Predicate}s that are evaluated for each Id value.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
public class VaultQueryCreator extends
|
||||
AbstractQueryCreator<KeyValueQuery<VaultQuery>, VaultQuery> {
|
||||
|
||||
/**
|
||||
* Create a new {@link VaultQueryCreator} given {@link PartTree}.
|
||||
*
|
||||
* @param tree must not be {@literal null}.
|
||||
*/
|
||||
public VaultQueryCreator(PartTree tree) {
|
||||
super(tree);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link VaultQueryCreator} given {@link PartTree} and
|
||||
* {@link ParameterAccessor}.
|
||||
*
|
||||
* @param tree must not be {@literal null}.
|
||||
* @param parameters must not be {@literal null}.
|
||||
*/
|
||||
public VaultQueryCreator(PartTree tree, ParameterAccessor parameters) {
|
||||
super(tree, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VaultQuery create(Part part, Iterator<Object> parameters) {
|
||||
return new VaultQuery(createPredicate(part, parameters), part.getProperty());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VaultQuery and(Part part, VaultQuery base, Iterator<Object> parameters) {
|
||||
|
||||
if (base == null) {
|
||||
return create(part, parameters);
|
||||
}
|
||||
return base.and(createPredicate(part, parameters), part.getProperty());
|
||||
}
|
||||
|
||||
private static Predicate<String> createPredicate(Part part,
|
||||
Iterator<Object> parameters) {
|
||||
|
||||
VariableAccessor accessor = getVariableAccessor(part);
|
||||
|
||||
Predicate<String> predicate = from(part, accessor, parameters);
|
||||
|
||||
return it -> predicate.test(accessor.toString(it));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Predicate} depending on the {@link Part} given.
|
||||
*
|
||||
* @param part
|
||||
* @param parameters
|
||||
* @return
|
||||
*/
|
||||
private static Predicate<String> from(Part part, VariableAccessor accessor,
|
||||
Iterator<Object> parameters) {
|
||||
|
||||
Type type = part.getType();
|
||||
|
||||
switch (type) {
|
||||
case AFTER:
|
||||
case GREATER_THAN:
|
||||
return new Criteria<>(accessor.nextString(parameters),
|
||||
(value, it) -> it.compareTo(value) > 0);
|
||||
case GREATER_THAN_EQUAL:
|
||||
return new Criteria<>(accessor.nextString(parameters),
|
||||
(value, it) -> it.compareTo(value) >= 0);
|
||||
case BEFORE:
|
||||
case LESS_THAN:
|
||||
return new Criteria<>(accessor.nextString(parameters),
|
||||
(value, it) -> it.compareTo(value) < 0);
|
||||
case LESS_THAN_EQUAL:
|
||||
return new Criteria<>(accessor.nextString(parameters),
|
||||
(value, it) -> it.compareTo(value) <= 0);
|
||||
case BETWEEN:
|
||||
|
||||
String from = accessor.nextString(parameters);
|
||||
String to = accessor.nextString(parameters);
|
||||
|
||||
return it -> it.compareTo(from) >= 0 && it.compareTo(to) <= 0;
|
||||
case NOT_IN:
|
||||
return new Criteria<>(accessor.nextAsArray(parameters),
|
||||
(value, it) -> Arrays.binarySearch(value, it) < 0);
|
||||
case IN:
|
||||
return new Criteria<>(accessor.nextAsArray(parameters),
|
||||
(value, it) -> Arrays.binarySearch(value, it) >= 0);
|
||||
case STARTING_WITH:
|
||||
return new Criteria<>(accessor.nextString(parameters),
|
||||
(value, it) -> it.startsWith(value));
|
||||
case ENDING_WITH:
|
||||
return new Criteria<>(accessor.nextString(parameters),
|
||||
(value, it) -> it.endsWith(value));
|
||||
case CONTAINING:
|
||||
return new Criteria<>(accessor.nextString(parameters),
|
||||
(value, it) -> it.contains(value));
|
||||
case NOT_CONTAINING:
|
||||
return new Criteria<>(accessor.nextString(parameters),
|
||||
(value, it) -> !it.contains(value));
|
||||
case REGEX:
|
||||
return Pattern.compile((String) parameters.next(),
|
||||
isIgnoreCase(part) ? Pattern.CASE_INSENSITIVE : 0).asPredicate();
|
||||
case TRUE:
|
||||
return it -> it.equalsIgnoreCase("true");
|
||||
case FALSE:
|
||||
return it -> it.equalsIgnoreCase("false");
|
||||
case SIMPLE_PROPERTY:
|
||||
return new Criteria<>(accessor.nextString(parameters),
|
||||
(value, it) -> it.equals(value));
|
||||
case NEGATING_SIMPLE_PROPERTY:
|
||||
return new Criteria<>(accessor.nextString(parameters),
|
||||
(value, it) -> !it.equals(value));
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported keyword!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VaultQuery or(VaultQuery vaultQuery, VaultQuery other) {
|
||||
return vaultQuery.or(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected KeyValueQuery<VaultQuery> complete(VaultQuery vaultQuery, Sort sort) {
|
||||
|
||||
KeyValueQuery<VaultQuery> query = new KeyValueQuery<>(vaultQuery);
|
||||
|
||||
if (sort != null) {
|
||||
query.orderBy(sort);
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
private static VariableAccessor getVariableAccessor(Part part) {
|
||||
return isIgnoreCase(part) ? VariableAccessor.Lowercase : VariableAccessor.AsIs;
|
||||
}
|
||||
|
||||
private static boolean isIgnoreCase(Part part) {
|
||||
return part.shouldIgnoreCase() != IgnoreCaseType.NEVER;
|
||||
}
|
||||
|
||||
@Value
|
||||
static class Criteria<T> implements Predicate<String> {
|
||||
|
||||
private T value;
|
||||
private BiPredicate<T, String> predicate;
|
||||
|
||||
@Override
|
||||
public boolean test(String s) {
|
||||
return predicate.test(value, s);
|
||||
}
|
||||
}
|
||||
|
||||
enum VariableAccessor {
|
||||
|
||||
AsIs {
|
||||
|
||||
@Override
|
||||
String nextString(Iterator<Object> parameters) {
|
||||
return parameters.next().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
String[] nextAsArray(Iterator<Object> iterator) {
|
||||
|
||||
Object next = iterator.next();
|
||||
|
||||
if (next instanceof Collection) {
|
||||
return ((Collection<?>) next).toArray(new String[0]);
|
||||
}
|
||||
else if (next != null && next.getClass().isArray()) {
|
||||
return (String[]) next;
|
||||
}
|
||||
|
||||
return new String[] { (String) next };
|
||||
}
|
||||
|
||||
@Override
|
||||
String toString(String value) {
|
||||
return value;
|
||||
}
|
||||
},
|
||||
|
||||
Lowercase {
|
||||
|
||||
@Override
|
||||
String nextString(Iterator<Object> parameters) {
|
||||
return AsIs.nextString(parameters).toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
String[] nextAsArray(Iterator<Object> iterator) {
|
||||
|
||||
String[] original = AsIs.nextAsArray(iterator);
|
||||
String[] lowercase = new String[original.length];
|
||||
|
||||
for (int i = 0; i < original.length; i++) {
|
||||
lowercase[i] = original[i].toLowerCase();
|
||||
}
|
||||
|
||||
return lowercase;
|
||||
}
|
||||
|
||||
@Override
|
||||
String toString(String value) {
|
||||
return value.toLowerCase();
|
||||
}
|
||||
};
|
||||
|
||||
abstract String[] nextAsArray(Iterator<Object> iterator);
|
||||
|
||||
abstract String nextString(Iterator<Object> iterator);
|
||||
|
||||
abstract String toString(String value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.support;
|
||||
|
||||
import org.springframework.data.keyvalue.core.KeyValueOperations;
|
||||
import org.springframework.data.keyvalue.repository.query.KeyValuePartTreeQuery;
|
||||
import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactory;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
|
||||
import org.springframework.vault.repository.core.MappingVaultEntityInformation;
|
||||
import org.springframework.vault.repository.mapping.VaultPersistentEntity;
|
||||
import org.springframework.vault.repository.query.VaultQueryCreator;
|
||||
|
||||
/**
|
||||
* {@link RepositoryFactorySupport} specific of handing Vault
|
||||
* {@link org.springframework.data.keyvalue.repository.KeyValueRepository}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.0
|
||||
*/
|
||||
public class VaultRepositoryFactory extends KeyValueRepositoryFactory {
|
||||
|
||||
private final KeyValueOperations operations;
|
||||
|
||||
public VaultRepositoryFactory(KeyValueOperations keyValueOperations) {
|
||||
this(keyValueOperations, VaultQueryCreator.class);
|
||||
}
|
||||
|
||||
public VaultRepositoryFactory(KeyValueOperations keyValueOperations,
|
||||
Class<? extends AbstractQueryCreator<?, ?>> queryCreator) {
|
||||
this(keyValueOperations, queryCreator, KeyValuePartTreeQuery.class);
|
||||
}
|
||||
|
||||
public VaultRepositoryFactory(KeyValueOperations keyValueOperations,
|
||||
Class<? extends AbstractQueryCreator<?, ?>> queryCreator,
|
||||
Class<? extends RepositoryQuery> repositoryQueryType) {
|
||||
super(keyValueOperations, queryCreator, repositoryQueryType);
|
||||
|
||||
this.operations = keyValueOperations;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T, ID> EntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
|
||||
|
||||
VaultPersistentEntity<T> entity = (VaultPersistentEntity<T>) operations
|
||||
.getMappingContext().getPersistentEntity(domainClass);
|
||||
|
||||
return new MappingVaultEntityInformation<>(entity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.support;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.data.keyvalue.core.KeyValueOperations;
|
||||
import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactoryBean;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
|
||||
|
||||
/**
|
||||
* Adapter for Springs {@link FactoryBean} interface to allow easy setup of
|
||||
* {@link VaultRepositoryFactory} via Spring configuration.
|
||||
*
|
||||
* @param <T> The repository type.
|
||||
* @param <S> The repository domain type.
|
||||
* @param <ID> The repository id type.
|
||||
* @since 2.0
|
||||
*/
|
||||
public class VaultRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable>
|
||||
extends KeyValueRepositoryFactoryBean<T, S, ID> {
|
||||
|
||||
/**
|
||||
* Creates a new {@link VaultRepositoryFactoryBean} for the given repository
|
||||
* interface.
|
||||
*
|
||||
* @param repositoryInterface must not be {@literal null}.
|
||||
*/
|
||||
public VaultRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
|
||||
super(repositoryInterface);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VaultRepositoryFactory createRepositoryFactory(
|
||||
KeyValueOperations operations,
|
||||
Class<? extends AbstractQueryCreator<?, ?>> queryCreator,
|
||||
Class<? extends RepositoryQuery> repositoryQueryType) {
|
||||
|
||||
return new VaultRepositoryFactory(operations, queryCreator, repositoryQueryType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.vault.authentication.ClientAuthentication;
|
||||
import org.springframework.vault.authentication.TokenAuthentication;
|
||||
import org.springframework.vault.client.VaultEndpoint;
|
||||
import org.springframework.vault.config.AbstractVaultConfiguration;
|
||||
import org.springframework.vault.support.SslConfiguration;
|
||||
import org.springframework.vault.util.Settings;
|
||||
|
||||
/**
|
||||
* Test configuration for Vault integration tests.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Configuration
|
||||
public class VaultIntegrationTestConfiguration extends AbstractVaultConfiguration {
|
||||
|
||||
@Override
|
||||
public VaultEndpoint vaultEndpoint() {
|
||||
return new VaultEndpoint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientAuthentication clientAuthentication() {
|
||||
return new TokenAuthentication(Settings.token());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SslConfiguration sslConfiguration() {
|
||||
return Settings.createSslConfiguration();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Data;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.vault.core.VaultTemplate;
|
||||
import org.springframework.vault.repository.VaultRepositoryIntegrationTests.VaultRepositoryTestConfiguration;
|
||||
import org.springframework.vault.repository.configuration.EnableVaultRepositories;
|
||||
import org.springframework.vault.util.IntegrationTestSupport;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.data.domain.Sort.Order.asc;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = VaultRepositoryTestConfiguration.class)
|
||||
public class VaultRepositoryIntegrationTests extends IntegrationTestSupport {
|
||||
|
||||
@Configuration
|
||||
@EnableVaultRepositories(considerNestedRepositories = true)
|
||||
static class VaultRepositoryTestConfiguration extends
|
||||
VaultIntegrationTestConfiguration {
|
||||
}
|
||||
|
||||
@Autowired
|
||||
VaultRepository vaultRepository;
|
||||
|
||||
@Autowired
|
||||
VaultTemplate vaultTemplate;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
vaultRepository.deleteAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadAndSave() {
|
||||
|
||||
Person person = new Person();
|
||||
person.setId("foo-key");
|
||||
person.setName("bar");
|
||||
|
||||
vaultRepository.save(person);
|
||||
|
||||
Iterable<Person> all = vaultRepository.findAll();
|
||||
|
||||
assertThat(all).contains(person);
|
||||
assertThat(vaultRepository.findById("foo-key")).contains(person);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldApplyQueryMethod() {
|
||||
|
||||
Person walter = new Person();
|
||||
walter.setId("walter");
|
||||
walter.setName("Walter");
|
||||
|
||||
vaultRepository.save(walter);
|
||||
|
||||
Person skyler = new Person();
|
||||
skyler.setId("skyler");
|
||||
skyler.setName("Skyler");
|
||||
|
||||
vaultRepository.save(skyler);
|
||||
|
||||
Iterable<Person> all = vaultRepository.findByIdStartsWith("walt");
|
||||
|
||||
assertThat(all).contains(walter).doesNotContain(skyler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldApplyQueryMethodWithSorting() {
|
||||
|
||||
Person walter = new Person();
|
||||
walter.setId("walter");
|
||||
walter.setName("Walter");
|
||||
|
||||
vaultRepository.save(walter);
|
||||
|
||||
Person skyler = new Person();
|
||||
skyler.setId("skyler");
|
||||
skyler.setName("Skyler");
|
||||
|
||||
vaultRepository.save(skyler);
|
||||
|
||||
assertThat(vaultRepository.findAllByOrderByNameAsc()).containsSequence(skyler,
|
||||
walter);
|
||||
assertThat(vaultRepository.findAllByOrderByNameDesc()).containsSequence(walter,
|
||||
skyler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldApplyLimiting() {
|
||||
|
||||
Person walter = new Person();
|
||||
walter.setId("walter");
|
||||
walter.setName("Walter");
|
||||
|
||||
vaultRepository.save(walter);
|
||||
|
||||
Person skyler = new Person();
|
||||
skyler.setId("skyler");
|
||||
skyler.setName("Skyler");
|
||||
|
||||
vaultRepository.save(skyler);
|
||||
|
||||
assertThat(vaultRepository.findTop1By(Sort.by(asc("name")))).containsOnly(skyler);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidDataAccessApiUsageException.class)
|
||||
public void shouldFailForNonIdCriteria() {
|
||||
vaultRepository.findInvalidByName("foo");
|
||||
}
|
||||
|
||||
interface VaultRepository extends CrudRepository<Person, String> {
|
||||
|
||||
List<Person> findByIdStartsWith(String prefix);
|
||||
|
||||
List<Person> findAllByOrderByNameAsc();
|
||||
|
||||
List<Person> findAllByOrderByNameDesc();
|
||||
|
||||
List<Person> findTop1By(Sort sort);
|
||||
|
||||
List<Person> findInvalidByName(String name);
|
||||
}
|
||||
|
||||
@Data
|
||||
static class Person {
|
||||
|
||||
String id, name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.convert;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.data.convert.ConfigurableTypeInformationMapper;
|
||||
import org.springframework.data.convert.SimpleTypeInformationMapper;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DefaultVaultTypeMapper}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class DefaultVaultTypeMapperUnitTests {
|
||||
|
||||
ConfigurableTypeInformationMapper configurableTypeInformationMapper = new ConfigurableTypeInformationMapper(
|
||||
Collections.singletonMap(String.class, "1"));
|
||||
SimpleTypeInformationMapper simpleTypeInformationMapper = new SimpleTypeInformationMapper();
|
||||
DefaultVaultTypeMapper typeMapper = new DefaultVaultTypeMapper();
|
||||
|
||||
@Test
|
||||
public void defaultInstanceWritesClasses() {
|
||||
|
||||
writesTypeToField(new LinkedHashMap<>(), String.class, String.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultInstanceReadsClasses() {
|
||||
|
||||
Map<String, Object> document = new LinkedHashMap<>();
|
||||
document.put(DefaultVaultTypeMapper.DEFAULT_TYPE_KEY, String.class.getName());
|
||||
|
||||
readsTypeFromField(document, String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writesMapKeyForType() {
|
||||
|
||||
typeMapper = new DefaultVaultTypeMapper(DefaultVaultTypeMapper.DEFAULT_TYPE_KEY,
|
||||
Collections.singletonList(configurableTypeInformationMapper));
|
||||
|
||||
writesTypeToField(new LinkedHashMap<>(), String.class, "1");
|
||||
writesTypeToField(new LinkedHashMap<>(), Object.class, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writesClassNamesForUnmappedValuesIfConfigured() {
|
||||
|
||||
typeMapper = new DefaultVaultTypeMapper(DefaultVaultTypeMapper.DEFAULT_TYPE_KEY,
|
||||
Arrays.asList(configurableTypeInformationMapper,
|
||||
simpleTypeInformationMapper));
|
||||
writesTypeToField(new LinkedHashMap<>(), String.class, "1");
|
||||
writesTypeToField(new LinkedHashMap<>(), Object.class, Object.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readsTypeForMapKey() {
|
||||
|
||||
typeMapper = new DefaultVaultTypeMapper(DefaultVaultTypeMapper.DEFAULT_TYPE_KEY,
|
||||
Collections.singletonList(configurableTypeInformationMapper));
|
||||
|
||||
readsTypeFromField(
|
||||
Collections.singletonMap(DefaultVaultTypeMapper.DEFAULT_TYPE_KEY, "1"),
|
||||
String.class);
|
||||
readsTypeFromField(Collections.singletonMap(
|
||||
DefaultVaultTypeMapper.DEFAULT_TYPE_KEY, "unmapped"), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readsTypeLoadingClassesForUnmappedTypesIfConfigured() {
|
||||
|
||||
typeMapper = new DefaultVaultTypeMapper(DefaultVaultTypeMapper.DEFAULT_TYPE_KEY,
|
||||
Arrays.asList(configurableTypeInformationMapper,
|
||||
simpleTypeInformationMapper));
|
||||
|
||||
readsTypeFromField(
|
||||
Collections.singletonMap(DefaultVaultTypeMapper.DEFAULT_TYPE_KEY, "1"),
|
||||
String.class);
|
||||
readsTypeFromField(Collections.singletonMap(
|
||||
DefaultVaultTypeMapper.DEFAULT_TYPE_KEY, Object.class.getName()),
|
||||
Object.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addsFullyQualifiedClassNameUnderDefaultKeyByDefault() {
|
||||
writesTypeToField(DefaultVaultTypeMapper.DEFAULT_TYPE_KEY, new LinkedHashMap<>(),
|
||||
String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writesTypeToCustomFieldIfConfigured() {
|
||||
|
||||
typeMapper = new DefaultVaultTypeMapper("_custom");
|
||||
writesTypeToField("_custom", new LinkedHashMap<>(), String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotWriteTypeInformationInCaseKeyIsSetToNull() {
|
||||
|
||||
typeMapper = new DefaultVaultTypeMapper(null);
|
||||
writesTypeToField(null, new LinkedHashMap<>(), String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readsTypeFromDefaultKeyByDefault() {
|
||||
readsTypeFromField(Collections.singletonMap(
|
||||
DefaultVaultTypeMapper.DEFAULT_TYPE_KEY, String.class.getName()),
|
||||
String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readsTypeFromCustomFieldConfigured() {
|
||||
|
||||
typeMapper = new DefaultVaultTypeMapper("_custom");
|
||||
readsTypeFromField(Collections.singletonMap("_custom", String.class.getName()),
|
||||
String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnsNullIfNoTypeInfoInDocument() {
|
||||
readsTypeFromField(new LinkedHashMap<>(), null);
|
||||
readsTypeFromField(
|
||||
Collections.singletonMap(DefaultVaultTypeMapper.DEFAULT_TYPE_KEY, ""),
|
||||
null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnsNullIfClassCannotBeLoaded() {
|
||||
readsTypeFromField(Collections.singletonMap(
|
||||
DefaultVaultTypeMapper.DEFAULT_TYPE_KEY, "fooBar"), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnsNullIfTypeKeySetToNull() {
|
||||
typeMapper = new DefaultVaultTypeMapper(null);
|
||||
readsTypeFromField(Collections.singletonMap(
|
||||
DefaultVaultTypeMapper.DEFAULT_TYPE_KEY, String.class), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnsCorrectTypeKey() {
|
||||
|
||||
assertThat(typeMapper.isTypeKey(DefaultVaultTypeMapper.DEFAULT_TYPE_KEY))
|
||||
.isTrue();
|
||||
|
||||
typeMapper = new DefaultVaultTypeMapper("_custom");
|
||||
assertThat(typeMapper.isTypeKey("_custom")).isTrue();
|
||||
assertThat(typeMapper.isTypeKey(DefaultVaultTypeMapper.DEFAULT_TYPE_KEY))
|
||||
.isFalse();
|
||||
|
||||
typeMapper = new DefaultVaultTypeMapper(null);
|
||||
assertThat(typeMapper.isTypeKey("_custom")).isFalse();
|
||||
assertThat(typeMapper.isTypeKey(DefaultVaultTypeMapper.DEFAULT_TYPE_KEY))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
private void readsTypeFromField(Map<String, Object> document, @Nullable Class<?> type) {
|
||||
|
||||
TypeInformation<?> typeInfo = typeMapper.readType(document);
|
||||
|
||||
if (type != null) {
|
||||
assertThat(typeInfo).isNotNull();
|
||||
assertThat(typeInfo.getType()).isAssignableFrom(type);
|
||||
}
|
||||
else {
|
||||
assertThat(typeInfo).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
private void writesTypeToField(@Nullable String field, Map<String, Object> document,
|
||||
Class<?> type) {
|
||||
|
||||
typeMapper.writeType(type, document);
|
||||
|
||||
if (field == null) {
|
||||
assertThat(document.keySet()).isEmpty();
|
||||
}
|
||||
else {
|
||||
assertThat(document).containsKey(field);
|
||||
assertThat(document).containsEntry(field, type.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private void writesTypeToField(Map<String, Object> document, Class<?> type,
|
||||
@Nullable Object value) {
|
||||
|
||||
typeMapper.writeType(type, document);
|
||||
|
||||
if (value == null) {
|
||||
assertThat(document.keySet()).isEmpty();
|
||||
}
|
||||
else {
|
||||
assertThat(document).containsKey(DefaultVaultTypeMapper.DEFAULT_TYPE_KEY);
|
||||
assertThat(document).containsEntry(DefaultVaultTypeMapper.DEFAULT_TYPE_KEY,
|
||||
value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.convert;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.vault.repository.mapping.VaultMappingContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link MappingVaultConverter}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class MappingVaultConverterUnitTests {
|
||||
|
||||
VaultMappingContext context = new VaultMappingContext();
|
||||
|
||||
MappingVaultConverter converter = new MappingVaultConverter(context);
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
|
||||
VaultCustomConversions conversions = new VaultCustomConversions(Arrays.asList(
|
||||
DocumentToPersonConverter.INSTANCE, PersonToDocumentConverter.INSTANCE));
|
||||
converter.setCustomConversions(conversions);
|
||||
converter.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReadSimpleEntity() {
|
||||
|
||||
SecretDocument document = new SecretDocument("heisenberg");
|
||||
document.put("username", "walter");
|
||||
document.put("password", "hb");
|
||||
|
||||
SimpleEntity entity = converter.read(SimpleEntity.class, document);
|
||||
|
||||
assertThat(entity.getId()).isEqualTo("heisenberg");
|
||||
assertThat(entity.getUsername()).isEqualTo("walter");
|
||||
assertThat(entity.getPassword()).isEqualTo("hb");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReadConvertedEntity() {
|
||||
|
||||
SecretDocument document = new SecretDocument("heisenberg");
|
||||
document.put("the_name", "walter");
|
||||
|
||||
Person entity = converter.read(Person.class, document);
|
||||
|
||||
assertThat(entity.getName()).isEqualTo("walter");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReadSubtype() {
|
||||
|
||||
SecretDocument document = new SecretDocument("heisenberg");
|
||||
document.put("_class", ExtendedEntity.class.getName());
|
||||
document.put("username", "walter");
|
||||
document.put("password", "hb");
|
||||
document.put("location", "Albuquerque");
|
||||
|
||||
SimpleEntity entity = converter.read(SimpleEntity.class, document);
|
||||
|
||||
assertThat(entity).isInstanceOf(ExtendedEntity.class);
|
||||
assertThat(entity.getId()).isEqualTo("heisenberg");
|
||||
assertThat(entity.getUsername()).isEqualTo("walter");
|
||||
assertThat(entity.getPassword()).isEqualTo("hb");
|
||||
assertThat(((ExtendedEntity) entity).getLocation()).isEqualTo("Albuquerque");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReadEntityWithEnum() {
|
||||
|
||||
SecretDocument document = new SecretDocument();
|
||||
document.put("condition", "BAD");
|
||||
|
||||
EntityWithEnum entity = converter.read(EntityWithEnum.class, document);
|
||||
|
||||
assertThat(entity.getCondition()).isEqualTo(Condition.BAD);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReadSimpleEntityWithConstructorCreation() {
|
||||
|
||||
SecretDocument document = new SecretDocument("heisenberg");
|
||||
document.put("username", "walter");
|
||||
document.put("password", "hb");
|
||||
|
||||
ConstructorCreation entity = converter.read(ConstructorCreation.class, document);
|
||||
|
||||
assertThat(entity.getId()).isEqualTo("heisenberg");
|
||||
assertThat(entity.getUsername()).isEqualTo("walter");
|
||||
assertThat(entity.getPassword()).isEqualTo("hb");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReadEntityWithList() {
|
||||
|
||||
SecretDocument document = new SecretDocument(Collections.singletonMap(
|
||||
"usernames", Arrays.asList("walter", "heisenberg")));
|
||||
|
||||
EntityWithListOfStrings entity = converter.read(EntityWithListOfStrings.class,
|
||||
document);
|
||||
|
||||
assertThat(entity.getUsernames()).containsSequence("walter", "heisenberg");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReadEntityWithMap() {
|
||||
|
||||
Map<String, Integer> keyVersions = new LinkedHashMap<>();
|
||||
keyVersions.put("foo", 1);
|
||||
keyVersions.put("bar", 2);
|
||||
|
||||
SecretDocument document = new SecretDocument(Collections.singletonMap(
|
||||
"keyVersions", keyVersions));
|
||||
|
||||
EntityWithMap entity = converter.read(EntityWithMap.class, document);
|
||||
|
||||
assertThat(entity.getKeyVersions()).containsAllEntriesOf(keyVersions);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReadEntityWithNesting() {
|
||||
|
||||
Map<String, String> walter = new LinkedHashMap<>();
|
||||
walter.put("username", "heisenberg");
|
||||
walter.put("password", "hb");
|
||||
|
||||
SecretDocument document = new SecretDocument();
|
||||
document.put("nested", walter);
|
||||
|
||||
EntityWithNestedType entity = converter
|
||||
.read(EntityWithNestedType.class, document);
|
||||
|
||||
assertThat(entity.getNested()).isEqualTo(new NestedType("heisenberg", "hb"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReadEntityWithListOfEntities() {
|
||||
|
||||
Map<String, String> walter = new LinkedHashMap<>();
|
||||
walter.put("username", "heisenberg");
|
||||
walter.put("password", "hb");
|
||||
|
||||
Map<String, String> skyler = new LinkedHashMap<>();
|
||||
skyler.put("username", "skyler");
|
||||
skyler.put("password", "marie");
|
||||
|
||||
SecretDocument document = new SecretDocument();
|
||||
document.put("nested", Arrays.asList(walter, skyler));
|
||||
|
||||
EntityWithListOfEntities entity = converter.read(EntityWithListOfEntities.class,
|
||||
document);
|
||||
|
||||
assertThat(entity.getNested()).contains(new NestedType("heisenberg", "hb"),
|
||||
new NestedType("skyler", "marie"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldWriteSimpleEntity() {
|
||||
|
||||
SimpleEntity entity = new SimpleEntity();
|
||||
entity.setId("heisenberg");
|
||||
entity.setUsername("walter");
|
||||
entity.setPassword("hb");
|
||||
|
||||
SecretDocument expected = new SecretDocument("heisenberg");
|
||||
expected.put("username", "walter");
|
||||
expected.put("password", "hb");
|
||||
expected.put("_class", entity.getClass().getName());
|
||||
|
||||
SecretDocument sink = new SecretDocument();
|
||||
|
||||
converter.write(entity, sink);
|
||||
|
||||
assertThat(sink).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldWriteConvertedEntity() {
|
||||
|
||||
SecretDocument expected = new SecretDocument();
|
||||
expected.put("the_name", "walter");
|
||||
|
||||
SecretDocument sink = new SecretDocument();
|
||||
|
||||
converter.write(new Person("walter"), sink);
|
||||
|
||||
assertThat(sink).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldWriteEntityWithEnum() {
|
||||
|
||||
EntityWithEnum entity = new EntityWithEnum();
|
||||
entity.setCondition(Condition.BAD);
|
||||
|
||||
SecretDocument sink = new SecretDocument();
|
||||
|
||||
converter.write(entity, sink);
|
||||
|
||||
assertThat(sink.getBody()).containsEntry("condition", "BAD");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldWriteEntityWithList() {
|
||||
|
||||
EntityWithListOfStrings entity = new EntityWithListOfStrings();
|
||||
entity.setUsernames(Arrays.asList("walter", "heisenberg"));
|
||||
|
||||
SecretDocument sink = new SecretDocument();
|
||||
|
||||
converter.write(entity, sink);
|
||||
|
||||
assertThat(sink.getBody()).containsEntry("usernames",
|
||||
Arrays.asList("walter", "heisenberg"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldWriteEntityWithMap() {
|
||||
|
||||
Map<String, Integer> keyVersions = new LinkedHashMap<>();
|
||||
keyVersions.put("foo", 1);
|
||||
keyVersions.put("bar", 2);
|
||||
|
||||
EntityWithMap entity = new EntityWithMap();
|
||||
entity.setKeyVersions(keyVersions);
|
||||
|
||||
SecretDocument sink = new SecretDocument();
|
||||
|
||||
converter.write(entity, sink);
|
||||
|
||||
assertThat(sink.getBody()).containsEntry("keyVersions", keyVersions);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void shouldWriteEntityWithListOfEntities() {
|
||||
|
||||
EntityWithListOfEntities entity = new EntityWithListOfEntities();
|
||||
entity.setNested(Arrays.asList(new NestedType("heisenberg", "hb"),
|
||||
new NestedType("skyler", "marie")));
|
||||
|
||||
Map<String, Object> walter = new LinkedHashMap<>();
|
||||
walter.put("username", "heisenberg");
|
||||
walter.put("password", "hb");
|
||||
|
||||
Map<String, Object> skyler = new LinkedHashMap<>();
|
||||
skyler.put("username", "skyler");
|
||||
skyler.put("password", "marie");
|
||||
|
||||
SecretDocument sink = new SecretDocument();
|
||||
|
||||
converter.write(entity, sink);
|
||||
|
||||
assertThat((List<Map<String, Object>>) sink.get("nested")).contains(walter,
|
||||
skyler);
|
||||
}
|
||||
|
||||
@Data
|
||||
static class SimpleEntity {
|
||||
|
||||
String id;
|
||||
String username;
|
||||
String password;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class ExtendedEntity extends SimpleEntity {
|
||||
|
||||
String location;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class EntityWithNestedType {
|
||||
|
||||
NestedType nested;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class EntityWithEnum {
|
||||
|
||||
Condition condition;
|
||||
}
|
||||
|
||||
@Data
|
||||
@RequiredArgsConstructor
|
||||
static class ConstructorCreation {
|
||||
|
||||
final String id;
|
||||
final String username;
|
||||
String password;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class EntityWithListOfStrings {
|
||||
|
||||
List<String> usernames;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class EntityWithListOfEntities {
|
||||
|
||||
List<NestedType> nested;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class EntityWithMap {
|
||||
|
||||
Map<String, Integer> keyVersions;
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
static class NestedType {
|
||||
|
||||
String username;
|
||||
String password;
|
||||
}
|
||||
|
||||
enum Condition {
|
||||
GOOD, BAD
|
||||
}
|
||||
|
||||
@Data
|
||||
static class Person {
|
||||
final String name;
|
||||
}
|
||||
|
||||
enum DocumentToPersonConverter implements Converter<SecretDocument, Person> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Person convert(SecretDocument secretDocument) {
|
||||
return new Person((String) secretDocument.get("the_name"));
|
||||
}
|
||||
}
|
||||
|
||||
enum PersonToDocumentConverter implements Converter<Person, SecretDocument> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public SecretDocument convert(Person person) {
|
||||
|
||||
SecretDocument document = new SecretDocument();
|
||||
document.put("the_name", person.getName());
|
||||
return document;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.mapping;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link BasicVaultPersistentEntity} via {@link VaultMappingContext}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class BasicVaultPersistentEntityUnitTests {
|
||||
|
||||
private VaultMappingContext mappingContext = new VaultMappingContext();
|
||||
|
||||
@Test
|
||||
public void shouldSetIdPropertyThroughName() {
|
||||
|
||||
VaultPersistentEntity<?> persistentEntity = mappingContext
|
||||
.getPersistentEntity(IdProperty.class);
|
||||
|
||||
assertThat(persistentEntity.getIdProperty()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSetIdPropertyThroughAnnotation() {
|
||||
|
||||
VaultPersistentEntity<?> persistentEntity = mappingContext
|
||||
.getPersistentEntity(ExplicitId.class);
|
||||
|
||||
assertThat(persistentEntity.getIdProperty()).isNotNull();
|
||||
}
|
||||
|
||||
static class IdProperty {
|
||||
String id, username;
|
||||
}
|
||||
|
||||
static class ExplicitId {
|
||||
@Id
|
||||
String username;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.mapping;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link VaultMappingContext}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class VaultMappingContextUnitTests {
|
||||
|
||||
VaultMappingContext context = new VaultMappingContext();
|
||||
|
||||
@Test
|
||||
public void shouldCreatePersistentEntity() {
|
||||
|
||||
VaultPersistentEntity<?> entity = context.getPersistentEntity(Person.class);
|
||||
|
||||
assertThat(entity).isNotNull();
|
||||
assertThat(entity.getSecretBackend()).isEqualTo("secret");
|
||||
assertThat(entity.getKeySpace()).isEqualTo("secret/person");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDetermineKeyspace() {
|
||||
|
||||
assertThat(context.getRequiredPersistentEntity(Login.class).getSecretBackend())
|
||||
.isEqualTo("secret");
|
||||
assertThat(context.getRequiredPersistentEntity(Login.class).getKeySpace())
|
||||
.isEqualTo("secret/login");
|
||||
|
||||
assertThat(
|
||||
context.getRequiredPersistentEntity(Credentials.class).getSecretBackend())
|
||||
.isEqualTo("shared");
|
||||
assertThat(context.getRequiredPersistentEntity(Credentials.class).getKeySpace())
|
||||
.isEqualTo("shared/Email");
|
||||
}
|
||||
|
||||
private static class Person {
|
||||
}
|
||||
|
||||
@Secret
|
||||
private static class Login {
|
||||
}
|
||||
|
||||
@Secret(value = "Email", backend = "shared")
|
||||
private static class Credentials {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Copyright 2017 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.repository.query;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.data.repository.query.DefaultParameters;
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit test for {@link VaultQueryCreator}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class VaultQueryCreatorUnitTests {
|
||||
|
||||
@Test
|
||||
public void greaterThan() {
|
||||
|
||||
VaultQuery query = createQuery("findByIdGreaterThan", "5");
|
||||
|
||||
assertThat(query.getPredicate()).accepts("6", "7").rejects("4", "5");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void greaterThanOrEqual() {
|
||||
|
||||
VaultQuery query = createQuery("findByIdGreaterThanEqual", "5");
|
||||
|
||||
assertThat(query.getPredicate()).accepts("5", "6", "7").rejects("4");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lessThan() {
|
||||
|
||||
VaultQuery query = createQuery("findByIdLessThan", "5");
|
||||
|
||||
assertThat(query.getPredicate()).accepts("4").rejects("5", "6", "7");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lessThanOrEqual() {
|
||||
|
||||
VaultQuery query = createQuery("findByIdLessThanEqual", "5");
|
||||
|
||||
assertThat(query.getPredicate()).accepts("4", "5").rejects("6", "7");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void between() {
|
||||
|
||||
VaultQuery query = createQuery("findByIdIsBetween", "5", "7");
|
||||
|
||||
assertThat(query.getPredicate()).accepts("5", "6", "7").rejects("4", "8");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void in() {
|
||||
|
||||
VaultQuery query = createQuery("findByIdIn", Arrays.asList("2", "3"));
|
||||
|
||||
assertThat(query.getPredicate()).accepts("2", "3").rejects("4", "8");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void negateIn() {
|
||||
|
||||
VaultQuery query = createQuery("findByIdNotIn", Arrays.asList("2", "3"));
|
||||
|
||||
assertThat(query.getPredicate()).accepts("4", "8").rejects("2", "3");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startingWith() {
|
||||
|
||||
VaultQuery query = createQuery("findByIdStartsWith", "Walter");
|
||||
|
||||
assertThat(query.getPredicate()).accepts("Walter White").rejects("Skyler");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void endingWith() {
|
||||
|
||||
VaultQuery query = createQuery("findByIdEndsWith", "White");
|
||||
|
||||
assertThat(query.getPredicate()).accepts("Walter White").rejects("Skyler");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containing() {
|
||||
|
||||
VaultQuery query = createQuery("findByIdContaining", "er Wh");
|
||||
|
||||
assertThat(query.getPredicate()).accepts("Walter White").rejects("Skyler");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void negateContaining() {
|
||||
|
||||
VaultQuery query = createQuery("findByIdNotContaining", "er Wh");
|
||||
|
||||
assertThat(query.getPredicate()).accepts("Skyler").rejects("Walter White");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void regex() {
|
||||
|
||||
VaultQuery query = createQuery("findByIdMatches", "Wa(.*)r");
|
||||
|
||||
assertThat(query.getPredicate()).accepts("Walter", "Water").rejects("Skyler");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isTrue() {
|
||||
|
||||
VaultQuery query = createQuery("findByIdIsTrue", "");
|
||||
|
||||
assertThat(query.getPredicate()).accepts("true", "True").rejects("false");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isFalse() {
|
||||
|
||||
VaultQuery query = createQuery("findByIdIsFalse", "");
|
||||
|
||||
assertThat(query.getPredicate()).accepts("false", "False").rejects("true");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleProperty() {
|
||||
|
||||
VaultQuery query = createQuery("findById", "Walter");
|
||||
|
||||
assertThat(query.getPredicate()).accepts("Walter").rejects("Skyler");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void negateSimpleProperty() {
|
||||
|
||||
VaultQuery query = createQuery("findByIdNot", "Walter");
|
||||
|
||||
assertThat(query.getPredicate()).accepts("Skyler").rejects("Walter");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void greaterThanOrEquals() {
|
||||
|
||||
VaultQuery query = createQuery("findByIdGreaterThanOrIdIs", "5", "2");
|
||||
|
||||
assertThat(query.getPredicate()).accepts("6", "7", "2").rejects("3", "4", "5");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void greaterThanAndLessThan() {
|
||||
|
||||
VaultQuery query = createQuery("findByIdGreaterThanAndIdLessThan", "2", "5");
|
||||
|
||||
assertThat(query.getPredicate()).accepts("3", "4").rejects("2", "5", "6");
|
||||
}
|
||||
|
||||
VaultQuery createQuery(String methodName, String value) {
|
||||
|
||||
DefaultParameters defaultParameters = new DefaultParameters(
|
||||
ReflectionUtils.findMethod(dummy.class, "someUnrelatedMethod",
|
||||
String.class));
|
||||
|
||||
PartTree partTree = new PartTree(methodName, Credentials.class);
|
||||
VaultQueryCreator queryCreator = new VaultQueryCreator(
|
||||
partTree,
|
||||
new ParametersParameterAccessor(defaultParameters, new Object[] { value }));
|
||||
|
||||
return queryCreator.createQuery().getCriteria();
|
||||
}
|
||||
|
||||
VaultQuery createQuery(String methodName, List<String> value) {
|
||||
|
||||
DefaultParameters defaultParameters = new DefaultParameters(
|
||||
ReflectionUtils
|
||||
.findMethod(dummy.class, "someUnrelatedMethod", List.class));
|
||||
|
||||
PartTree partTree = new PartTree(methodName, Credentials.class);
|
||||
VaultQueryCreator queryCreator = new VaultQueryCreator(
|
||||
partTree,
|
||||
new ParametersParameterAccessor(defaultParameters, new Object[] { value }));
|
||||
|
||||
return queryCreator.createQuery().getCriteria();
|
||||
}
|
||||
|
||||
VaultQuery createQuery(String methodName, String value, String anotherValue) {
|
||||
|
||||
DefaultParameters defaultParameters = new DefaultParameters(
|
||||
ReflectionUtils.findMethod(dummy.class, "someUnrelatedMethod",
|
||||
String.class, String.class));
|
||||
|
||||
PartTree partTree = new PartTree(methodName, Credentials.class);
|
||||
VaultQueryCreator queryCreator = new VaultQueryCreator(partTree,
|
||||
new ParametersParameterAccessor(defaultParameters, new Object[] { value,
|
||||
anotherValue }));
|
||||
|
||||
return queryCreator.createQuery().getCriteria();
|
||||
}
|
||||
|
||||
static interface dummy {
|
||||
|
||||
Object someUnrelatedMethod(String arg);
|
||||
|
||||
Object someUnrelatedMethod(List<String> arg);
|
||||
|
||||
Object someUnrelatedMethod(String from, String to);
|
||||
}
|
||||
|
||||
static class Credentials {
|
||||
|
||||
String id;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
* Authentication steps DSL to <<vault.authentication.steps,compose authentication flows>>.
|
||||
* Reactive Vault client via `ReactiveVaultOperations`.
|
||||
* <<vault.repositories,Vault repository support>> based on Spring Data KeyValue.
|
||||
|
||||
[[new-features.1-0-0]]
|
||||
=== What's new in Spring Vault 1.0
|
||||
|
||||
276
src/main/asciidoc/reference/vault-repositories.adoc
Normal file
276
src/main/asciidoc/reference/vault-repositories.adoc
Normal file
@@ -0,0 +1,276 @@
|
||||
[[vault.repositories]]
|
||||
= Vault Repositories
|
||||
|
||||
Working with `VaultTemplate` and responses mapped to Java classes allows basic data operations like read, write
|
||||
and delete. Vault repositories apply Spring Data's repository concept on top of Vault.
|
||||
A Vault repository exposes basic CRUD functionality and supports query derivation with predicates constraining
|
||||
the Id property, paging and sorting.
|
||||
|
||||
[[vault.repositories.usage]]
|
||||
== Usage
|
||||
|
||||
To access domain entities stored in Vault you can leverage repository support that eases implementing those quite significantly.
|
||||
|
||||
.Sample Credentials Entity
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Secret
|
||||
public class Credentials {
|
||||
|
||||
@Id String id;
|
||||
String password;
|
||||
String socialSecurityNumber;
|
||||
Address address;
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
We have a pretty simple domain object here. Note that it has a property named `id` annotated with
|
||||
`org.springframework.data.annotation.Id` and a `@Secret` annotation on its type.
|
||||
Those two are responsible for creating the actual key used to persist the object as JSON inside Vault.
|
||||
|
||||
NOTE: Properties annotated with `@Id` as well as those named `id` are considered as the identifier properties.
|
||||
Those with the annotation are favored over others.
|
||||
|
||||
The next step is to declare a repository interface that uses the domain object.
|
||||
|
||||
.Basic Repository Interface for `Credentials` entities
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public interface CredentialsRepository extends CrudRepository<Credentials, String> {
|
||||
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
As our repository extends `CrudRepository` it provides basic CRUD and query methods.
|
||||
The thing we need in between to glue things together is the according Spring configuration.
|
||||
|
||||
.JavaConfig for Vault Repositories
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
@EnableVaultRepositories
|
||||
public class ApplicationConfig {
|
||||
|
||||
@Bean
|
||||
public VaultTemplate vaultTemplate() {
|
||||
return new VaultTemplate(…);
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Given the setup above we can go on and inject `CredentialsRepository` into our components.
|
||||
|
||||
.Access to Person Entities
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Autowired CredentialsRepository repo;
|
||||
|
||||
public void basicCrudOperations() {
|
||||
|
||||
Credentials creds = new Credentials("heisenberg", "327215", "AAA-GG-SSSS");
|
||||
rand.setAddress(new Address("308 Negra Arroyo Lane", "Albuquerque", "New Mexico", "87104"));
|
||||
|
||||
repo.save(creds); <1>
|
||||
|
||||
repo.findOne(creds.getId()); <2>
|
||||
|
||||
repo.count(); <3>
|
||||
|
||||
repo.delete(creds); <4>
|
||||
}
|
||||
----
|
||||
<1> Stores properties of `Credentials` inside Vault Hash with a key pattern `keyspace/id`,
|
||||
in this case `credentials/heisenberg`, in the generic secret backend.
|
||||
<2> Uses the provided id to retrieve the object stored at `keyspace/id`.
|
||||
<3> Counts the total number of entities available within the keyspace _credentials_ defined by `@Secret` on `Credentials`.
|
||||
<4> Removes the key for the given object from Vault.
|
||||
====
|
||||
|
||||
[[vault.repositories.mapping]]
|
||||
== Object to Vault JSON Mapping
|
||||
|
||||
Vault repositories store objects in Vault using JSON as interchange format. Object mapping between JSON and
|
||||
the entity is done by `VaultConverter`. The converter reads and writes `SecretDocument` that contains the body
|
||||
from a `VaultResponse`. ``VaultResponse``s are read from Vault and the body is deserialized by
|
||||
Jackson into a `Map` of `String` and `Object`.
|
||||
The default `VaultConverter` implementation reads the `Map` with nested values, `List` and `Map` objects and
|
||||
converts these to entities and vice versa.
|
||||
|
||||
Given the `Credentials` type from the previous sections the default mapping is as follows:
|
||||
|
||||
====
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"_class": "org.example.Credentials", <1>
|
||||
"password", "327215", <2>
|
||||
"socialSecurityNumber": "AAA-GG-SSSS",
|
||||
"address": { <3>
|
||||
"street": "308 Negra Arroyo Lane",
|
||||
"city": "Albuquerque",
|
||||
"state": "New Mexico",
|
||||
"zip":"87104"
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> The `_class` attribute is included on root level as well as on any nested interface or abstract types.
|
||||
<2> Simple property values are mapped by path.
|
||||
<3> Properties of complex types are mapped as nested objects.
|
||||
====
|
||||
|
||||
NOTE: The `@Id` property must be mapped to `String`.
|
||||
|
||||
[cols="1,2,3", options="header"]
|
||||
.Default Mapping Rules
|
||||
|===
|
||||
| Type
|
||||
| Sample
|
||||
| Mapped Value
|
||||
|
||||
| Simple Type +
|
||||
(eg. String)
|
||||
| String firstname = "Walter";
|
||||
| firstname = "Walter"
|
||||
|
||||
| Complex Type +
|
||||
(eg. Address)
|
||||
| Address adress = new Address("308 Negra Arroyo Lane");
|
||||
| address: { "street": "308 Negra Arroyo Lane" }
|
||||
|
||||
| List +
|
||||
of Simple Type
|
||||
| List<String> nicknames = asList("walt", "heisenberg");
|
||||
| nicknames: ["walt", "heisenberg"]
|
||||
|
||||
| Map +
|
||||
of Simple Type
|
||||
| Map<String, Integer> atts = asMap("age", 51)
|
||||
| atts : {"age" : 51}
|
||||
|
||||
| List +
|
||||
of Complex Type
|
||||
| List<Address> addresses = asList(new Address("308…
|
||||
| address: [{ "street": "308 Negra Arroyo Lane" }, …]
|
||||
|
||||
|===
|
||||
|
||||
You can customize the mapping behavior by registering a `Converter` in `VaultCustomConversions`.
|
||||
Those converters can take care of converting from/to a type such as `LocalDate` as well as `SecretDocument`
|
||||
whereas the first one is suitable for converting simple properties and the last one complex types to their JSON
|
||||
representation. The second option offers full control over the resulting `SecretDocument`. Writing objects to `Vault`
|
||||
will delete the content and re-create the whole entry, so not mapped data will be lost.
|
||||
|
||||
[[vault.repositories.queries]]
|
||||
== Queries and Query Methods
|
||||
|
||||
Query methods allow automatic derivation of simple queries from the method name. Vault has no query engine but
|
||||
requires direct access of HTTP context paths. Vault query methods translate Vault's API possibilities to queries.
|
||||
A query method execution lists children under a context path, applies filtering to the Id, optionally limits the
|
||||
Id stream with offset/limit and applies sorting after fetching the results.
|
||||
|
||||
.Sample Repository Query Method
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public interface CredentialsRepository extends CrudRepository<Credentials, String> {
|
||||
|
||||
List<Credentials> findByIdStartsWith(String prefix);
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
NOTE: Query methods for Vault repositories support only queries with predicates on the `@Id` property.
|
||||
|
||||
Here's an overview of the keywords supported for Vault.
|
||||
|
||||
[cols="1,2" options="header"]
|
||||
.Supported keywords for query methods
|
||||
|===
|
||||
| Keyword
|
||||
| Sample
|
||||
|
||||
| `After`, `GreaterThan`
|
||||
| `findByIdGreaterThan(String id)`
|
||||
|
||||
| `GreaterThanEqual`
|
||||
| `findByIdGreaterThanEqual(String id)`
|
||||
|
||||
| `Before`, `LessThan`
|
||||
| `findByIdLessThan(String id)`
|
||||
|
||||
| `LessThanEqual`
|
||||
| `findByIdLessThanEqual(String id)`
|
||||
|
||||
| `Between`
|
||||
| `findByIdBetween(String from, String to)`
|
||||
|
||||
| `In`
|
||||
| `findByIdIn(Collection ids)`
|
||||
|
||||
| `NotIn`
|
||||
| `findByIdNotIn(Collection ids)`
|
||||
|
||||
| `Like`, `StartingWith`, `EndingWith`
|
||||
| `findByIdLike(String id)`
|
||||
|
||||
| `NotLike`, `IsNotLike`
|
||||
| `findByIdNotLike(String id)`
|
||||
|
||||
| `Containing`
|
||||
| `findByFirstnameContaining(String id)`
|
||||
|
||||
| `NotContaining`
|
||||
| `findByFirstnameNotContaining(String name)`
|
||||
|
||||
| `Regex`
|
||||
| `findByIdRegex(String id)`
|
||||
|
||||
| `(No keyword)`
|
||||
| `findById(String name)`
|
||||
|
||||
| `Not`
|
||||
| `findByIdNot(String id)`
|
||||
|
||||
| `And`
|
||||
| `findByLastnameAndFirstname`
|
||||
|
||||
| `Or`
|
||||
| `findByLastnameOrFirstname`
|
||||
|
||||
| `Is,Equals`
|
||||
| `findByFirstname`,`findByFirstnameIs`,`findByFirstnameEquals`
|
||||
|
||||
| `Top,First`
|
||||
| `findFirst10ByFirstname`,`findTop5ByFirstname`
|
||||
|===
|
||||
|
||||
=== Sorting and Paging
|
||||
|
||||
Query methods support sorting and paging by selecting in memory a sublist (offset/limit) Id's retrieved from
|
||||
a Vault context path. Sorting has is not limited to a particular field, unlike query method predicates.
|
||||
Unpaged sorting is applied after Id filtering and all resulting secrets are fetched from Vault. This way
|
||||
a query method fetches only results that are also returned as part of the result.
|
||||
|
||||
Using paging and sorting requires secret fetching before filtering the Id's which impacts performance.
|
||||
Sorting and paging guarantees to return the same result even if the natural order of Id returned by Vault changes.
|
||||
Therefore, all Id's are fetched from Vault first, then sorting is applied and afterwards filtering and offset/limiting.
|
||||
|
||||
.Paging and Sorting Repository
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public interface CredentialsRepository extends PagingAndSortingRepository<Credentials, String> {
|
||||
|
||||
List<Credentials> findTop10ByIdStartsWithOrderBySocialSecurityNumberDesc(String prefix);
|
||||
|
||||
List<Credentials> findByIdStarts(String prefix, Pageable pageRequest);
|
||||
}
|
||||
----
|
||||
====
|
||||
@@ -25,6 +25,8 @@ include::reactive-template.adoc[]
|
||||
|
||||
include::propertysource.adoc[]
|
||||
|
||||
include::vault-repositories.adoc[]
|
||||
|
||||
include::client-support.adoc[]
|
||||
|
||||
include::authentication.adoc[]
|
||||
|
||||
Reference in New Issue
Block a user