mirror of https://github.com/dapr/java-sdk.git
Adding Spring Boot enhancements, Sring Data Repository, Testcontainers (#1089)
* Adding Spring Boot enhancements, Sring Data Repository, Testcontainers Signed-off-by: Artur Ciocanu <ciocanu@adobe.com> * Try running ITs all at once Signed-off-by: Artur Ciocanu <ciocanu@adobe.com> * Ensure HTTP and GRPC endpoints are overriden Signed-off-by: Artur Ciocanu <ciocanu@adobe.com> --------- Signed-off-by: Artur Ciocanu <ciocanu@adobe.com> Co-authored-by: Artur Ciocanu <ciocanu@adobe.com>
This commit is contained in:
parent
a6923ed75c
commit
935f3be367
|
@ -0,0 +1,79 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>io.dapr.spring</groupId>
|
||||
<artifactId>dapr-spring-parent</artifactId>
|
||||
<version>0.13.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>dapr-spring-boot-autoconfigure</artifactId>
|
||||
<name>dapr-spring-boot-autoconfigure</name>
|
||||
<description>Dapr Spring Boot Autoconfigure</description>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.dapr.spring</groupId>
|
||||
<artifactId>dapr-spring-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dapr.spring</groupId>
|
||||
<artifactId>dapr-spring-data</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dapr.spring</groupId>
|
||||
<artifactId>dapr-spring-messaging</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-keyvalue</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.vaadin.external.google</groupId>
|
||||
<artifactId>android-json</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dapr</groupId>
|
||||
<artifactId>testcontainers-dapr</artifactId>
|
||||
<version>${dapr.sdk.alpha.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.spring.boot.autoconfigure.client;
|
||||
|
||||
import io.dapr.client.DaprClient;
|
||||
import io.dapr.client.DaprClientBuilder;
|
||||
import io.dapr.spring.core.client.DaprClientCustomizer;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass(DaprClient.class)
|
||||
public class DaprClientAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
DaprClientBuilderConfigurer daprClientBuilderConfigurer(ObjectProvider<DaprClientCustomizer> customizerProvider) {
|
||||
DaprClientBuilderConfigurer configurer = new DaprClientBuilderConfigurer();
|
||||
configurer.setDaprClientCustomizer(customizerProvider.orderedStream().collect(Collectors.toList()));
|
||||
|
||||
return configurer;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
DaprClientBuilder daprClientBuilder(DaprClientBuilderConfigurer daprClientBuilderConfigurer) {
|
||||
DaprClientBuilder builder = new DaprClientBuilder();
|
||||
|
||||
return daprClientBuilderConfigurer.configure(builder);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
DaprClient daprClient(DaprClientBuilder daprClientBuilder) {
|
||||
return daprClientBuilder.build();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.spring.boot.autoconfigure.client;
|
||||
|
||||
import io.dapr.client.DaprClientBuilder;
|
||||
import io.dapr.spring.core.client.DaprClientCustomizer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Builder for configuring a {@link DaprClientBuilder}.
|
||||
*/
|
||||
public class DaprClientBuilderConfigurer {
|
||||
|
||||
private List<DaprClientCustomizer> customizers;
|
||||
|
||||
void setDaprClientCustomizer(List<DaprClientCustomizer> customizers) {
|
||||
this.customizers = List.copyOf(customizers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the specified {@link DaprClientBuilder}. The builder can be further
|
||||
* tuned and default settings can be overridden.
|
||||
*
|
||||
* @param builder the {@link DaprClientBuilder} instance to configure
|
||||
* @return the configured builder
|
||||
*/
|
||||
public DaprClientBuilder configure(DaprClientBuilder builder) {
|
||||
applyCustomizers(builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
private void applyCustomizers(DaprClientBuilder builder) {
|
||||
if (this.customizers != null) {
|
||||
for (DaprClientCustomizer customizer : this.customizers) {
|
||||
customizer.customize(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.spring.boot.autoconfigure.pubsub;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
|
||||
@ConfigurationProperties(prefix = DaprPubSubProperties.CONFIG_PREFIX)
|
||||
public class DaprPubSubProperties {
|
||||
|
||||
public static final String CONFIG_PREFIX = "dapr.pubsub";
|
||||
|
||||
/**
|
||||
* Name of the PubSub Dapr component.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.spring.boot.autoconfigure.statestore;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@ConfigurationProperties(prefix = DaprStateStoreProperties.CONFIG_PREFIX)
|
||||
public class DaprStateStoreProperties {
|
||||
|
||||
public static final String CONFIG_PREFIX = "dapr.statestore";
|
||||
|
||||
/**
|
||||
* Name of the StateStore Dapr component.
|
||||
*/
|
||||
private String name;
|
||||
private String binding;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getBinding() {
|
||||
return binding;
|
||||
}
|
||||
|
||||
public void setBinding(String binding) {
|
||||
this.binding = binding;
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
io.dapr.spring.boot.autoconfigure.client.DaprClientAutoConfiguration
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.spring.boot.autoconfigure.client;
|
||||
|
||||
import io.dapr.client.DaprClient;
|
||||
import io.dapr.client.DaprClientBuilder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DaprClientAutoConfiguration}.
|
||||
*/
|
||||
class DaprClientAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(DaprClientAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void daprClientBuilderConfigurer() {
|
||||
contextRunner.run(context -> assertThat(context).hasSingleBean(DaprClientBuilderConfigurer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void daprClientBuilder() {
|
||||
contextRunner.run(context -> assertThat(context).hasSingleBean(DaprClientBuilder.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void daprClient() {
|
||||
contextRunner.run(context -> assertThat(context).hasSingleBean(DaprClient.class));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>io.dapr.spring</groupId>
|
||||
<artifactId>dapr-spring-parent</artifactId>
|
||||
<version>0.13.0-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>dapr-spring-boot-starter</artifactId>
|
||||
<name>dapr-spring-boot-starter</name>
|
||||
<description>Dapr Client Spring Boot Starter</description>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dapr.spring</groupId>
|
||||
<artifactId>dapr-spring-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dapr.spring</groupId>
|
||||
<artifactId>dapr-spring-boot-autoconfigure</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>io.dapr.spring</groupId>
|
||||
<artifactId>dapr-spring-parent</artifactId>
|
||||
<version>0.13.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>dapr-spring-core</artifactId>
|
||||
<name>dapr-spring-core</name>
|
||||
<description>Dapr Spring Core</description>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.spring.core.client;
|
||||
|
||||
import io.dapr.client.DaprClientBuilder;
|
||||
|
||||
/**
|
||||
* Callback interface that can be used to customize a {@link DaprClientBuilder}.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface DaprClientCustomizer {
|
||||
|
||||
/**
|
||||
* Callback to customize a {@link DaprClientBuilder} instance.
|
||||
*
|
||||
* @param daprClientBuilder the client builder to customize
|
||||
*/
|
||||
void customize(DaprClientBuilder daprClientBuilder);
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>io.dapr.spring</groupId>
|
||||
<artifactId>dapr-spring-parent</artifactId>
|
||||
<version>0.13.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>dapr-spring-data</artifactId>
|
||||
<name>dapr-spring-data</name>
|
||||
<description>Dapr Spring Data</description>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-keyvalue</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.spring.data;
|
||||
|
||||
import io.dapr.client.DaprClient;
|
||||
import io.dapr.client.domain.GetStateRequest;
|
||||
import io.dapr.client.domain.SaveStateRequest;
|
||||
import io.dapr.client.domain.State;
|
||||
import io.dapr.utils.TypeRef;
|
||||
import org.springframework.data.keyvalue.core.KeyValueAdapter;
|
||||
import org.springframework.data.util.CloseableIterator;
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class AbstractDaprKeyValueAdapter implements KeyValueAdapter {
|
||||
private static final Map<String, String> CONTENT_TYPE_META = Collections.singletonMap(
|
||||
"contentType", "application/json");
|
||||
|
||||
private final DaprClient daprClient;
|
||||
private final String stateStoreName;
|
||||
|
||||
protected AbstractDaprKeyValueAdapter(DaprClient daprClient, String stateStoreName) {
|
||||
Assert.notNull(daprClient, "DaprClient must not be null");
|
||||
Assert.hasText(stateStoreName, "State store name must not be empty");
|
||||
|
||||
this.daprClient = daprClient;
|
||||
this.stateStoreName = stateStoreName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
daprClient.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object put(Object id, Object item, String keyspace) {
|
||||
Assert.notNull(id, "Id must not be null");
|
||||
Assert.notNull(item, "Item must not be null");
|
||||
Assert.hasText(keyspace, "Keyspace must not be empty");
|
||||
|
||||
String key = resolveKey(keyspace, id);
|
||||
State<Object> state = new State<>(key, item, null, CONTENT_TYPE_META, null);
|
||||
SaveStateRequest request = new SaveStateRequest(stateStoreName).setStates(state);
|
||||
|
||||
daprClient.saveBulkState(request).block();
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object id, String keyspace) {
|
||||
return get(id, keyspace) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(Object id, String keyspace) {
|
||||
Assert.notNull(id, "Id must not be null");
|
||||
Assert.hasText(keyspace, "Keyspace must not be empty");
|
||||
|
||||
String key = resolveKey(keyspace, id);
|
||||
|
||||
return resolveValue(daprClient.getState(stateStoreName, key, Object.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T get(Object id, String keyspace, Class<T> type) {
|
||||
Assert.notNull(id, "Id must not be null");
|
||||
Assert.hasText(keyspace, "Keyspace must not be empty");
|
||||
Assert.notNull(type, "Type must not be null");
|
||||
|
||||
String key = resolveKey(keyspace, id);
|
||||
GetStateRequest stateRequest = new GetStateRequest(stateStoreName, key).setMetadata(CONTENT_TYPE_META);
|
||||
|
||||
return resolveValue(daprClient.getState(stateRequest, TypeRef.get(type)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object delete(Object id, String keyspace) {
|
||||
Object result = get(id, keyspace);
|
||||
|
||||
if (result == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String key = resolveKey(keyspace, id);
|
||||
|
||||
daprClient.deleteState(stateStoreName, key).block();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T delete(Object id, String keyspace, Class<T> type) {
|
||||
T result = get(id, keyspace, type);
|
||||
|
||||
if (result == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String key = resolveKey(keyspace, id);
|
||||
|
||||
daprClient.deleteState(stateStoreName, key).block();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<?> getAllOf(String keyspace) {
|
||||
return getAllOf(keyspace, Object.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloseableIterator<Map.Entry<Object, Object>> entries(String keyspace) {
|
||||
throw new UnsupportedOperationException("'entries' method is not supported");
|
||||
}
|
||||
|
||||
private String resolveKey(String keyspace, Object id) {
|
||||
return String.format("%s-%s", keyspace, id);
|
||||
}
|
||||
|
||||
private <T> T resolveValue(Mono<State<T>> state) {
|
||||
if (state == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return state.blockOptional().map(State::getValue).orElse(null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.spring.data;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.dapr.client.DaprClient;
|
||||
import io.dapr.client.domain.ComponentMetadata;
|
||||
import io.dapr.client.domain.DaprMetadata;
|
||||
import org.springframework.data.keyvalue.core.KeyValueAdapter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class DaprKeyValueAdapterResolver implements KeyValueAdapterResolver {
|
||||
private static final Set<String> MYSQL_MARKERS = Set.of("state.mysql-v1", "bindings.mysql-v1");
|
||||
private static final Set<String> POSTGRESQL_MARKERS = Set.of("state.postgresql-v1", "bindings.postgresql-v1");
|
||||
private final DaprClient daprClient;
|
||||
private final ObjectMapper mapper;
|
||||
private final String stateStoreName;
|
||||
private final String bindingName;
|
||||
|
||||
/**
|
||||
* Constructs a {@link DaprKeyValueAdapterResolver}.
|
||||
*
|
||||
* @param daprClient The Dapr client.
|
||||
* @param mapper The object mapper.
|
||||
* @param stateStoreName The state store name.
|
||||
* @param bindingName The binding name.
|
||||
*/
|
||||
public DaprKeyValueAdapterResolver(DaprClient daprClient, ObjectMapper mapper, String stateStoreName,
|
||||
String bindingName) {
|
||||
this.daprClient = daprClient;
|
||||
this.mapper = mapper;
|
||||
this.stateStoreName = stateStoreName;
|
||||
this.bindingName = bindingName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyValueAdapter resolve() {
|
||||
DaprMetadata metadata = daprClient.getMetadata().block();
|
||||
|
||||
if (metadata == null) {
|
||||
throw new IllegalStateException("No Dapr metadata found");
|
||||
}
|
||||
|
||||
List<ComponentMetadata> components = metadata.getComponents();
|
||||
|
||||
if (components == null || components.isEmpty()) {
|
||||
throw new IllegalStateException("No components found in Dapr metadata");
|
||||
}
|
||||
|
||||
if (shouldUseMySQL(components, stateStoreName, bindingName)) {
|
||||
return new MySQLDaprKeyValueAdapter(daprClient, mapper, stateStoreName, bindingName);
|
||||
}
|
||||
|
||||
if (shouldUsePostgreSQL(components, stateStoreName, bindingName)) {
|
||||
return new PostgreSQLDaprKeyValueAdapter(daprClient, mapper, stateStoreName, bindingName);
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Could find any adapter matching the given state store and binding");
|
||||
}
|
||||
|
||||
@SuppressWarnings("AbbreviationAsWordInName")
|
||||
private boolean shouldUseMySQL(List<ComponentMetadata> components, String stateStoreName, String bindingName) {
|
||||
boolean stateStoreMatched = components.stream().anyMatch(x -> matchBy(stateStoreName, MYSQL_MARKERS, x));
|
||||
boolean bindingMatched = components.stream().anyMatch(x -> matchBy(bindingName, MYSQL_MARKERS, x));
|
||||
|
||||
return stateStoreMatched && bindingMatched;
|
||||
}
|
||||
|
||||
@SuppressWarnings("AbbreviationAsWordInName")
|
||||
private boolean shouldUsePostgreSQL(List<ComponentMetadata> components, String stateStoreName, String bindingName) {
|
||||
boolean stateStoreMatched = components.stream().anyMatch(x -> matchBy(stateStoreName, POSTGRESQL_MARKERS, x));
|
||||
boolean bindingMatched = components.stream().anyMatch(x -> matchBy(bindingName, POSTGRESQL_MARKERS, x));
|
||||
|
||||
return stateStoreMatched && bindingMatched;
|
||||
}
|
||||
|
||||
private boolean matchBy(String name, Set<String> markers, ComponentMetadata componentMetadata) {
|
||||
return componentMetadata.getName().equals(name) && markers.contains(getTypeAndVersion(componentMetadata));
|
||||
}
|
||||
|
||||
private String getTypeAndVersion(ComponentMetadata component) {
|
||||
return component.getType() + "-" + component.getVersion();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,402 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.spring.data;
|
||||
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.dao.support.PersistenceExceptionTranslator;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.keyvalue.core.IdentifierGenerator;
|
||||
import org.springframework.data.keyvalue.core.KeyValueAdapter;
|
||||
import org.springframework.data.keyvalue.core.KeyValueCallback;
|
||||
import org.springframework.data.keyvalue.core.KeyValueOperations;
|
||||
import org.springframework.data.keyvalue.core.KeyValuePersistenceExceptionTranslator;
|
||||
import org.springframework.data.keyvalue.core.event.KeyValueEvent;
|
||||
import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentEntity;
|
||||
import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentProperty;
|
||||
import org.springframework.data.keyvalue.core.mapping.context.KeyValueMappingContext;
|
||||
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
public class DaprKeyValueTemplate implements KeyValueOperations, ApplicationEventPublisherAware {
|
||||
|
||||
private static final PersistenceExceptionTranslator DEFAULT_PERSISTENCE_EXCEPTION_TRANSLATOR =
|
||||
new KeyValuePersistenceExceptionTranslator();
|
||||
|
||||
private final KeyValueAdapter adapter;
|
||||
private final MappingContext<? extends KeyValuePersistentEntity<?, ?>, ? extends KeyValuePersistentProperty<?>>
|
||||
mappingContext;
|
||||
private final IdentifierGenerator identifierGenerator;
|
||||
|
||||
private PersistenceExceptionTranslator exceptionTranslator = DEFAULT_PERSISTENCE_EXCEPTION_TRANSLATOR;
|
||||
private @Nullable ApplicationEventPublisher eventPublisher;
|
||||
private boolean publishEvents = false;
|
||||
private @SuppressWarnings("rawtypes") Set<Class<? extends KeyValueEvent>> eventTypesToPublish = Collections
|
||||
.emptySet();
|
||||
|
||||
/**
|
||||
* Create new {@link DaprKeyValueTemplate} using the given {@link KeyValueAdapterResolver} with a default
|
||||
* {@link KeyValueMappingContext}.
|
||||
*
|
||||
* @param resolver must not be {@literal null}.
|
||||
*/
|
||||
public DaprKeyValueTemplate(KeyValueAdapterResolver resolver) {
|
||||
this(resolver, new KeyValueMappingContext<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link DaprKeyValueTemplate} using the given {@link KeyValueAdapterResolver} and {@link MappingContext}.
|
||||
*
|
||||
* @param resolver must not be {@literal null}.
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
*/
|
||||
@SuppressWarnings("LineLength")
|
||||
public DaprKeyValueTemplate(KeyValueAdapterResolver resolver,
|
||||
MappingContext<? extends KeyValuePersistentEntity<?, ?>, ? extends KeyValuePersistentProperty<?>> mappingContext) {
|
||||
this(resolver, mappingContext, DefaultIdentifierGenerator.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link DaprKeyValueTemplate} using the given {@link KeyValueAdapterResolver} and {@link MappingContext}.
|
||||
*
|
||||
* @param resolver must not be {@literal null}.
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
* @param identifierGenerator must not be {@literal null}.
|
||||
*/
|
||||
@SuppressWarnings("LineLength")
|
||||
public DaprKeyValueTemplate(KeyValueAdapterResolver resolver,
|
||||
MappingContext<? extends KeyValuePersistentEntity<?, ?>, ? extends KeyValuePersistentProperty<?>> mappingContext,
|
||||
IdentifierGenerator identifierGenerator) {
|
||||
Assert.notNull(resolver, "Resolver must not be null");
|
||||
Assert.notNull(mappingContext, "MappingContext must not be null");
|
||||
Assert.notNull(identifierGenerator, "IdentifierGenerator must not be null");
|
||||
|
||||
this.adapter = resolver.resolve();
|
||||
this.mappingContext = mappingContext;
|
||||
this.identifierGenerator = identifierGenerator;
|
||||
}
|
||||
|
||||
private static boolean typeCheck(Class<?> requiredType, @Nullable Object candidate) {
|
||||
return candidate == null || ClassUtils.isAssignable(requiredType, candidate.getClass());
|
||||
}
|
||||
|
||||
public void setExceptionTranslator(PersistenceExceptionTranslator exceptionTranslator) {
|
||||
Assert.notNull(exceptionTranslator, "ExceptionTranslator must not be null");
|
||||
this.exceptionTranslator = exceptionTranslator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ApplicationEventPublisher} to be used to publish {@link KeyValueEvent}s.
|
||||
*
|
||||
* @param eventTypesToPublish must not be {@literal null}.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void setEventTypesToPublish(Set<Class<? extends KeyValueEvent>> eventTypesToPublish) {
|
||||
if (CollectionUtils.isEmpty(eventTypesToPublish)) {
|
||||
this.publishEvents = false;
|
||||
} else {
|
||||
this.publishEvents = true;
|
||||
this.eventTypesToPublish = Collections.unmodifiableSet(eventTypesToPublish);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
|
||||
this.eventPublisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T insert(T objectToInsert) {
|
||||
KeyValuePersistentEntity<?, ?> entity = getKeyValuePersistentEntity(objectToInsert);
|
||||
GeneratingIdAccessor generatingIdAccessor = new GeneratingIdAccessor(
|
||||
entity.getPropertyAccessor(objectToInsert),
|
||||
entity.getIdProperty(),
|
||||
identifierGenerator
|
||||
);
|
||||
Object id = generatingIdAccessor.getOrGenerateIdentifier();
|
||||
|
||||
return insert(id, objectToInsert);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T insert(Object id, T objectToInsert) {
|
||||
Assert.notNull(id, "Id for object to be inserted must not be null");
|
||||
Assert.notNull(objectToInsert, "Object to be inserted must not be null");
|
||||
|
||||
String keyspace = resolveKeySpace(objectToInsert.getClass());
|
||||
|
||||
potentiallyPublishEvent(KeyValueEvent.beforeInsert(id, keyspace, objectToInsert.getClass(), objectToInsert));
|
||||
|
||||
execute((KeyValueCallback<Void>) adapter -> {
|
||||
|
||||
if (adapter.contains(id, keyspace)) {
|
||||
throw new DuplicateKeyException(
|
||||
String.format("Cannot insert existing object with id %s; Please use update", id));
|
||||
}
|
||||
|
||||
adapter.put(id, objectToInsert, keyspace);
|
||||
return null;
|
||||
});
|
||||
|
||||
potentiallyPublishEvent(KeyValueEvent.afterInsert(id, keyspace, objectToInsert.getClass(), objectToInsert));
|
||||
|
||||
return objectToInsert;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T update(T objectToUpdate) {
|
||||
KeyValuePersistentEntity<?, ?> entity = getKeyValuePersistentEntity(objectToUpdate);
|
||||
|
||||
if (!entity.hasIdProperty()) {
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
String.format("Cannot determine id for type %s", ClassUtils.getUserClass(objectToUpdate)));
|
||||
}
|
||||
|
||||
return update(entity.getIdentifierAccessor(objectToUpdate).getRequiredIdentifier(), objectToUpdate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T update(Object id, T objectToUpdate) {
|
||||
Assert.notNull(id, "Id for object to be inserted must not be null");
|
||||
Assert.notNull(objectToUpdate, "Object to be updated must not be null");
|
||||
|
||||
String keyspace = resolveKeySpace(objectToUpdate.getClass());
|
||||
|
||||
potentiallyPublishEvent(KeyValueEvent.beforeUpdate(id, keyspace, objectToUpdate.getClass(), objectToUpdate));
|
||||
|
||||
Object existing = execute(adapter -> adapter.put(id, objectToUpdate, keyspace));
|
||||
|
||||
potentiallyPublishEvent(
|
||||
KeyValueEvent.afterUpdate(id, keyspace, objectToUpdate.getClass(), objectToUpdate, existing));
|
||||
|
||||
return objectToUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> findById(Object id, Class<T> type) {
|
||||
Assert.notNull(id, "Id for object to be found must not be null");
|
||||
Assert.notNull(type, "Type to fetch must not be null");
|
||||
|
||||
String keyspace = resolveKeySpace(type);
|
||||
|
||||
potentiallyPublishEvent(KeyValueEvent.beforeGet(id, keyspace, type));
|
||||
|
||||
T result = execute(adapter -> {
|
||||
Object value = adapter.get(id, keyspace, type);
|
||||
|
||||
if (value == null || typeCheck(type, value)) {
|
||||
return type.cast(value);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
potentiallyPublishEvent(KeyValueEvent.afterGet(id, keyspace, type, result));
|
||||
|
||||
return Optional.ofNullable(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Class<?> type) {
|
||||
Assert.notNull(type, "Type to delete must not be null");
|
||||
|
||||
String keyspace = resolveKeySpace(type);
|
||||
|
||||
potentiallyPublishEvent(KeyValueEvent.beforeDropKeySpace(keyspace, type));
|
||||
|
||||
execute((KeyValueCallback<Void>) adapter -> {
|
||||
|
||||
adapter.deleteAllOf(keyspace);
|
||||
return null;
|
||||
});
|
||||
|
||||
potentiallyPublishEvent(KeyValueEvent.afterDropKeySpace(keyspace, type));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> T delete(T objectToDelete) {
|
||||
Class<T> type = (Class<T>) ClassUtils.getUserClass(objectToDelete);
|
||||
KeyValuePersistentEntity<?, ?> entity = getKeyValuePersistentEntity(objectToDelete);
|
||||
Object id = entity.getIdentifierAccessor(objectToDelete).getIdentifier();
|
||||
|
||||
if (id == null) {
|
||||
String error = String.format("Cannot determine id for type %s", ClassUtils.getUserClass(objectToDelete));
|
||||
|
||||
throw new InvalidDataAccessApiUsageException(error);
|
||||
}
|
||||
|
||||
return delete(id, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T delete(Object id, Class<T> type) {
|
||||
Assert.notNull(id, "Id for object to be deleted must not be null");
|
||||
Assert.notNull(type, "Type to delete must not be null");
|
||||
|
||||
String keyspace = resolveKeySpace(type);
|
||||
|
||||
potentiallyPublishEvent(KeyValueEvent.beforeDelete(id, keyspace, type));
|
||||
|
||||
T result = execute(adapter -> adapter.delete(id, keyspace, type));
|
||||
|
||||
potentiallyPublishEvent(KeyValueEvent.afterDelete(id, keyspace, type, result));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public <T> T execute(KeyValueCallback<T> action) {
|
||||
Assert.notNull(action, "KeyValueCallback must not be null");
|
||||
|
||||
try {
|
||||
return action.doInKeyValue(this.adapter);
|
||||
} catch (RuntimeException e) {
|
||||
throw resolveExceptionIfPossible(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected <T> T executeRequired(KeyValueCallback<T> action) {
|
||||
T result = execute(action);
|
||||
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new IllegalStateException(String.format("KeyValueCallback %s returned null value", action));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Iterable<T> find(KeyValueQuery<?> query, Class<T> type) {
|
||||
return executeRequired((KeyValueCallback<Iterable<T>>) adapter -> {
|
||||
Iterable<?> result = adapter.find(query, resolveKeySpace(type), type);
|
||||
|
||||
List<T> filtered = new ArrayList<>();
|
||||
|
||||
for (Object candidate : result) {
|
||||
if (typeCheck(type, candidate)) {
|
||||
filtered.add(type.cast(candidate));
|
||||
}
|
||||
}
|
||||
|
||||
return filtered;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Iterable<T> findAll(Class<T> type) {
|
||||
Assert.notNull(type, "Type to fetch must not be null");
|
||||
|
||||
return executeRequired(adapter -> {
|
||||
Iterable<?> values = adapter.getAllOf(resolveKeySpace(type), type);
|
||||
|
||||
ArrayList<T> filtered = new ArrayList<>();
|
||||
for (Object candidate : values) {
|
||||
if (typeCheck(type, candidate)) {
|
||||
filtered.add(type.cast(candidate));
|
||||
}
|
||||
}
|
||||
|
||||
return filtered;
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public <T> Iterable<T> findAll(Sort sort, Class<T> type) {
|
||||
return find(new KeyValueQuery(sort), type);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public <T> Iterable<T> findInRange(long offset, int rows, Class<T> type) {
|
||||
return find(new KeyValueQuery().skip(offset).limit(rows), type);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public <T> Iterable<T> findInRange(long offset, int rows, Sort sort, Class<T> type) {
|
||||
return find(new KeyValueQuery(sort).skip(offset).limit(rows), type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count(Class<?> type) {
|
||||
Assert.notNull(type, "Type for count must not be null");
|
||||
return adapter.count(resolveKeySpace(type));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count(KeyValueQuery<?> query, Class<?> type) {
|
||||
return executeRequired(adapter -> adapter.count(query, resolveKeySpace(type)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(KeyValueQuery<?> query, Class<?> type) {
|
||||
return executeRequired(adapter -> adapter.exists(query, resolveKeySpace(type)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MappingContext<?, ?> getMappingContext() {
|
||||
return this.mappingContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyValueAdapter getKeyValueAdapter() {
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
this.adapter.destroy();
|
||||
}
|
||||
|
||||
private KeyValuePersistentEntity<?, ?> getKeyValuePersistentEntity(Object objectToInsert) {
|
||||
return this.mappingContext.getRequiredPersistentEntity(ClassUtils.getUserClass(objectToInsert));
|
||||
}
|
||||
|
||||
private String resolveKeySpace(Class<?> type) {
|
||||
return this.mappingContext.getRequiredPersistentEntity(type).getKeySpace();
|
||||
}
|
||||
|
||||
private RuntimeException resolveExceptionIfPossible(RuntimeException e) {
|
||||
DataAccessException translatedException = exceptionTranslator.translateExceptionIfPossible(e);
|
||||
|
||||
return translatedException != null ? translatedException : e;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private void potentiallyPublishEvent(KeyValueEvent event) {
|
||||
if (eventPublisher == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (publishEvents && (eventTypesToPublish.isEmpty() || eventTypesToPublish.contains(event.getClass()))) {
|
||||
eventPublisher.publishEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.spring.data;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.keyvalue.core.IdentifierGenerator;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link IdentifierGenerator} to generate identifiers of types {@link UUID}.
|
||||
*/
|
||||
enum DefaultIdentifierGenerator implements IdentifierGenerator {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
private final AtomicReference<SecureRandom> secureRandom = new AtomicReference<>(null);
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T generateIdentifierOfType(TypeInformation<T> identifierType) {
|
||||
|
||||
Class<?> type = identifierType.getType();
|
||||
|
||||
if (ClassUtils.isAssignable(UUID.class, type)) {
|
||||
return (T) UUID.randomUUID();
|
||||
} else if (ClassUtils.isAssignable(String.class, type)) {
|
||||
return (T) UUID.randomUUID().toString();
|
||||
} else if (ClassUtils.isAssignable(Integer.class, type)) {
|
||||
return (T) Integer.valueOf(getSecureRandom().nextInt());
|
||||
} else if (ClassUtils.isAssignable(Long.class, type)) {
|
||||
return (T) Long.valueOf(getSecureRandom().nextLong());
|
||||
}
|
||||
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
String.format("Identifier cannot be generated for %s; Supported types are: UUID, String, Integer, and Long",
|
||||
identifierType.getType().getName()));
|
||||
}
|
||||
|
||||
private SecureRandom getSecureRandom() {
|
||||
|
||||
SecureRandom secureRandom = this.secureRandom.get();
|
||||
if (secureRandom != null) {
|
||||
return secureRandom;
|
||||
}
|
||||
|
||||
for (String algorithm : OsTools.secureRandomAlgorithmNames()) {
|
||||
try {
|
||||
secureRandom = SecureRandom.getInstance(algorithm);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// ignore and try next.
|
||||
}
|
||||
}
|
||||
|
||||
if (secureRandom == null) {
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
String.format("Could not create SecureRandom instance for one of the algorithms '%s'",
|
||||
StringUtils.collectionToCommaDelimitedString(OsTools.secureRandomAlgorithmNames())));
|
||||
}
|
||||
|
||||
this.secureRandom.compareAndSet(null, secureRandom);
|
||||
|
||||
return secureRandom;
|
||||
}
|
||||
|
||||
private static class OsTools {
|
||||
|
||||
private static final String OPERATING_SYSTEM_NAME = System.getProperty("os.name").toLowerCase();
|
||||
|
||||
private static final List<String> SECURE_RANDOM_ALGORITHMS_LINUX_OSX_SOLARIS = Arrays.asList("NativePRNGBlocking",
|
||||
"NativePRNGNonBlocking", "NativePRNG", "SHA1PRNG");
|
||||
private static final List<String> SECURE_RANDOM_ALGORITHMS_WINDOWS = Arrays.asList("SHA1PRNG", "Windows-PRNG");
|
||||
|
||||
static List<String> secureRandomAlgorithmNames() {
|
||||
return OPERATING_SYSTEM_NAME.contains("win") ? SECURE_RANDOM_ALGORITHMS_WINDOWS
|
||||
: SECURE_RANDOM_ALGORITHMS_LINUX_OSX_SOLARIS;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.spring.data;
|
||||
|
||||
import org.springframework.data.keyvalue.core.IdentifierGenerator;
|
||||
import org.springframework.data.mapping.IdentifierAccessor;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link IdentifierAccessor} adding a {@link #getOrGenerateIdentifier()} to automatically generate an identifier and
|
||||
* set it on the underling bean instance.
|
||||
*
|
||||
* @see #getOrGenerateIdentifier()
|
||||
*/
|
||||
class GeneratingIdAccessor implements IdentifierAccessor {
|
||||
|
||||
private final PersistentPropertyAccessor<?> accessor;
|
||||
private final PersistentProperty<?> identifierProperty;
|
||||
private final IdentifierGenerator generator;
|
||||
|
||||
/**
|
||||
* Creates a new {@link GeneratingIdAccessor} using the given {@link PersistentPropertyAccessor}, identifier property
|
||||
* and {@link IdentifierGenerator}.
|
||||
*
|
||||
* @param accessor must not be {@literal null}.
|
||||
* @param identifierProperty must not be {@literal null}.
|
||||
* @param generator must not be {@literal null}.
|
||||
*/
|
||||
GeneratingIdAccessor(PersistentPropertyAccessor<?> accessor, PersistentProperty<?> identifierProperty,
|
||||
IdentifierGenerator generator) {
|
||||
|
||||
Assert.notNull(accessor, "PersistentPropertyAccessor must not be null");
|
||||
Assert.notNull(identifierProperty, "Identifier property must not be null");
|
||||
Assert.notNull(generator, "IdentifierGenerator must not be null");
|
||||
|
||||
this.accessor = accessor;
|
||||
this.identifierProperty = identifierProperty;
|
||||
this.generator = generator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getIdentifier() {
|
||||
return accessor.getProperty(identifierProperty);
|
||||
}
|
||||
|
||||
Object getOrGenerateIdentifier() {
|
||||
|
||||
Object existingIdentifier = getIdentifier();
|
||||
|
||||
if (existingIdentifier != null) {
|
||||
return existingIdentifier;
|
||||
}
|
||||
|
||||
Object generatedIdentifier = generator.generateIdentifierOfType(identifierProperty.getTypeInformation());
|
||||
accessor.setProperty(identifierProperty, generatedIdentifier);
|
||||
|
||||
return generatedIdentifier;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.spring.data;
|
||||
|
||||
import org.springframework.data.keyvalue.core.KeyValueAdapter;
|
||||
|
||||
public interface KeyValueAdapterResolver {
|
||||
KeyValueAdapter resolve();
|
||||
}
|
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.spring.data;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonPointer;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.dapr.client.DaprClient;
|
||||
import io.dapr.utils.TypeRef;
|
||||
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
|
||||
import org.springframework.expression.spel.SpelNode;
|
||||
import org.springframework.expression.spel.standard.SpelExpression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.data.keyvalue.core.KeyValueAdapter} implementation for MySQL.
|
||||
*/
|
||||
@SuppressWarnings("AbbreviationAsWordInName")
|
||||
public class MySQLDaprKeyValueAdapter extends AbstractDaprKeyValueAdapter {
|
||||
private static final String DELETE_BY_KEYSPACE_PATTERN = "delete from state where id LIKE '%s'";
|
||||
private static final String SELECT_BY_KEYSPACE_PATTERN = "select value from state where id LIKE '%s'";
|
||||
private static final String SELECT_BY_FILTER_PATTERN =
|
||||
"select value from state where id LIKE '%s' and JSON_EXTRACT(value, %s) = %s";
|
||||
private static final String COUNT_BY_KEYSPACE_PATTERN = "select count(*) as value from state where id LIKE '%s'";
|
||||
private static final String COUNT_BY_FILTER_PATTERN =
|
||||
"select count(*) as value from state where id LIKE '%s' and JSON_EXTRACT(value, %s) = %s";
|
||||
|
||||
private static final TypeRef<List<JsonNode>> FILTER_TYPE_REF = new TypeRef<>() {
|
||||
};
|
||||
private static final TypeRef<List<JsonNode>> COUNT_TYPE_REF = new TypeRef<>() {
|
||||
};
|
||||
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
|
||||
private static final JsonPointer VALUE_POINTER = JsonPointer.compile("/value");
|
||||
|
||||
private final DaprClient daprClient;
|
||||
private final ObjectMapper mapper;
|
||||
private final String stateStoreName;
|
||||
private final String bindingName;
|
||||
|
||||
/**
|
||||
* Constructs a {@link MySQLDaprKeyValueAdapter}.
|
||||
*
|
||||
* @param daprClient The Dapr client.
|
||||
* @param mapper The object mapper.
|
||||
* @param stateStoreName The state store name.
|
||||
* @param bindingName The binding name.
|
||||
*/
|
||||
public MySQLDaprKeyValueAdapter(DaprClient daprClient, ObjectMapper mapper, String stateStoreName,
|
||||
String bindingName) {
|
||||
super(daprClient, stateStoreName);
|
||||
|
||||
Assert.notNull(mapper, "ObjectMapper must not be null");
|
||||
Assert.hasText(bindingName, "State store binding must not be empty");
|
||||
|
||||
this.daprClient = daprClient;
|
||||
this.mapper = mapper;
|
||||
this.stateStoreName = stateStoreName;
|
||||
this.bindingName = bindingName;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <T> Iterable<T> getAllOf(String keyspace, Class<T> type) {
|
||||
Assert.hasText(keyspace, "Keyspace must not be empty");
|
||||
Assert.notNull(type, "Type must not be null");
|
||||
|
||||
String sql = createSql(SELECT_BY_KEYSPACE_PATTERN, keyspace);
|
||||
List<JsonNode> result = queryUsingBinding(sql, FILTER_TYPE_REF);
|
||||
|
||||
return convertValues(result, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAllOf(String keyspace) {
|
||||
Assert.hasText(keyspace, "Keyspace must not be empty");
|
||||
|
||||
String sql = createSql(DELETE_BY_KEYSPACE_PATTERN, keyspace);
|
||||
|
||||
execUsingBinding(sql);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Iterable<T> find(KeyValueQuery<?> query, String keyspace, Class<T> type) {
|
||||
Assert.notNull(query, "Query must not be null");
|
||||
Assert.hasText(keyspace, "Keyspace must not be empty");
|
||||
Assert.notNull(type, "Type must not be null");
|
||||
|
||||
Object criteria = query.getCriteria();
|
||||
|
||||
if (criteria == null) {
|
||||
return getAllOf(keyspace, type);
|
||||
}
|
||||
|
||||
String sql = createSql(SELECT_BY_FILTER_PATTERN, keyspace, criteria);
|
||||
List<JsonNode> result = queryUsingBinding(sql, FILTER_TYPE_REF);
|
||||
|
||||
return convertValues(result, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count(String keyspace) {
|
||||
Assert.hasText(keyspace, "Keyspace must not be empty");
|
||||
|
||||
String sql = createSql(COUNT_BY_KEYSPACE_PATTERN, keyspace);
|
||||
List<JsonNode> result = queryUsingBinding(sql, COUNT_TYPE_REF);
|
||||
|
||||
return extractCount(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count(KeyValueQuery<?> query, String keyspace) {
|
||||
Assert.notNull(query, "Query must not be null");
|
||||
Assert.hasText(keyspace, "Keyspace must not be empty");
|
||||
|
||||
Object criteria = query.getCriteria();
|
||||
|
||||
if (criteria == null) {
|
||||
return count(keyspace);
|
||||
}
|
||||
|
||||
String sql = createSql(COUNT_BY_FILTER_PATTERN, keyspace, criteria);
|
||||
List<JsonNode> result = queryUsingBinding(sql, COUNT_TYPE_REF);
|
||||
|
||||
return extractCount(result);
|
||||
}
|
||||
|
||||
private String getKeyspaceFilter(String keyspace) {
|
||||
return String.format("%s||%s-%%", stateStoreName, keyspace);
|
||||
}
|
||||
|
||||
private String createSql(String sqlPattern, String keyspace) {
|
||||
String keyspaceFilter = getKeyspaceFilter(keyspace);
|
||||
|
||||
return String.format(sqlPattern, keyspaceFilter);
|
||||
}
|
||||
|
||||
private String createSql(String sqlPattern, String keyspace, Object criteria) {
|
||||
String keyspaceFilter = getKeyspaceFilter(keyspace);
|
||||
SpelExpression expression = PARSER.parseRaw(criteria.toString());
|
||||
SpelNode leftNode = expression.getAST().getChild(0);
|
||||
SpelNode rightNode = expression.getAST().getChild(1);
|
||||
String left = String.format("'$.%s'", leftNode.toStringAST());
|
||||
String right = rightNode.toStringAST();
|
||||
|
||||
return String.format(sqlPattern, keyspaceFilter, left, right);
|
||||
}
|
||||
|
||||
private void execUsingBinding(String sql) {
|
||||
Map<String, String> meta = Collections.singletonMap("sql", sql);
|
||||
|
||||
daprClient.invokeBinding(bindingName, "exec", null, meta).block();
|
||||
}
|
||||
|
||||
private <T> T queryUsingBinding(String sql, TypeRef<T> typeRef) {
|
||||
Map<String, String> meta = Collections.singletonMap("sql", sql);
|
||||
|
||||
return daprClient.invokeBinding(bindingName, "query", null, meta, typeRef).block();
|
||||
}
|
||||
|
||||
private <T> List<T> convertValues(List<JsonNode> values, Class<T> type) {
|
||||
if (values == null || values.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return values.stream()
|
||||
.map(value -> convertValue(value, type))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private <T> T convertValue(JsonNode value, Class<T> type) {
|
||||
JsonNode valueNode = value.at(VALUE_POINTER);
|
||||
|
||||
if (valueNode.isMissingNode()) {
|
||||
throw new IllegalStateException("Value is missing");
|
||||
}
|
||||
|
||||
try {
|
||||
// The value is stored as a base64 encoded string and wrapped in quotes
|
||||
// hence we need to remove the quotes and then decode
|
||||
String rawValue = valueNode.toString().replace("\"", "");
|
||||
byte[] decodedValue = Base64.getDecoder().decode(rawValue);
|
||||
|
||||
return mapper.readValue(decodedValue, type);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private long extractCount(List<JsonNode> values) {
|
||||
if (values == null || values.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
JsonNode valueNode = values.get(0).at(VALUE_POINTER);
|
||||
|
||||
if (valueNode.isMissingNode()) {
|
||||
throw new IllegalStateException("Count value is missing");
|
||||
}
|
||||
|
||||
if (!valueNode.isNumber()) {
|
||||
throw new IllegalStateException("Count value is not a number");
|
||||
}
|
||||
|
||||
return valueNode.asLong();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.spring.data;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.dapr.client.DaprClient;
|
||||
import io.dapr.spring.data.repository.query.DaprPredicate;
|
||||
import io.dapr.utils.TypeRef;
|
||||
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
|
||||
import org.springframework.expression.spel.SpelNode;
|
||||
import org.springframework.expression.spel.standard.SpelExpression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.data.keyvalue.core.KeyValueAdapter} implementation for PostgreSQL.
|
||||
*/
|
||||
@SuppressWarnings("AbbreviationAsWordInName")
|
||||
public class PostgreSQLDaprKeyValueAdapter extends AbstractDaprKeyValueAdapter {
|
||||
private static final String DELETE_BY_KEYSPACE_PATTERN = "delete from state where key LIKE '%s'";
|
||||
private static final String SELECT_BY_KEYSPACE_PATTERN = "select value from state where key LIKE '%s'";
|
||||
private static final String SELECT_BY_FILTER_PATTERN =
|
||||
"select value from state where key LIKE '%s' and JSONB_EXTRACT_PATH_TEXT(value, %s) = %s";
|
||||
private static final String COUNT_BY_KEYSPACE_PATTERN = "select count(*) as value from state where key LIKE '%s'";
|
||||
private static final String COUNT_BY_FILTER_PATTERN =
|
||||
"select count(*) as value from state where key LIKE '%s' and JSONB_EXTRACT_PATH_TEXT(value, %s) = %s";
|
||||
|
||||
private static final TypeRef<List<List<Object>>> FILTER_TYPE_REF = new TypeRef<>() {
|
||||
};
|
||||
private static final TypeRef<List<List<Long>>> COUNT_TYPE_REF = new TypeRef<>() {
|
||||
};
|
||||
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
|
||||
|
||||
private final DaprClient daprClient;
|
||||
private final ObjectMapper mapper;
|
||||
private final String stateStoreName;
|
||||
private final String bindingName;
|
||||
|
||||
/**
|
||||
* Constructs a {@link PostgreSQLDaprKeyValueAdapter}.
|
||||
*
|
||||
* @param daprClient The Dapr client.
|
||||
* @param mapper The object mapper.
|
||||
* @param stateStoreName The state store name.
|
||||
* @param bindingName The binding name.
|
||||
*/
|
||||
public PostgreSQLDaprKeyValueAdapter(DaprClient daprClient, ObjectMapper mapper, String stateStoreName,
|
||||
String bindingName) {
|
||||
super(daprClient, stateStoreName);
|
||||
|
||||
Assert.notNull(mapper, "ObjectMapper must not be null");
|
||||
Assert.hasText(bindingName, "State store binding must not be empty");
|
||||
|
||||
this.daprClient = daprClient;
|
||||
this.mapper = mapper;
|
||||
this.stateStoreName = stateStoreName;
|
||||
this.bindingName = bindingName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Iterable<T> getAllOf(String keyspace, Class<T> type) {
|
||||
Assert.hasText(keyspace, "Keyspace must not be empty");
|
||||
Assert.notNull(type, "Type must not be null");
|
||||
|
||||
String sql = createSql(SELECT_BY_KEYSPACE_PATTERN, keyspace);
|
||||
List<List<Object>> result = queryUsingBinding(sql, FILTER_TYPE_REF);
|
||||
|
||||
return convertValues(result, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAllOf(String keyspace) {
|
||||
Assert.hasText(keyspace, "Keyspace must not be empty");
|
||||
|
||||
String sql = createSql(DELETE_BY_KEYSPACE_PATTERN, keyspace);
|
||||
|
||||
execUsingBinding(sql);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Iterable<T> find(KeyValueQuery<?> query, String keyspace, Class<T> type) {
|
||||
Assert.notNull(query, "Query must not be null");
|
||||
Assert.hasText(keyspace, "Keyspace must not be empty");
|
||||
Assert.notNull(type, "Type must not be null");
|
||||
|
||||
Object criteria = query.getCriteria();
|
||||
|
||||
if (criteria == null) {
|
||||
return getAllOf(keyspace, type);
|
||||
}
|
||||
|
||||
String sql = createSql(SELECT_BY_FILTER_PATTERN, keyspace, criteria);
|
||||
List<List<Object>> result = queryUsingBinding(sql, FILTER_TYPE_REF);
|
||||
|
||||
return convertValues(result, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count(String keyspace) {
|
||||
Assert.hasText(keyspace, "Keyspace must not be empty");
|
||||
|
||||
String sql = createSql(COUNT_BY_KEYSPACE_PATTERN, keyspace);
|
||||
List<List<Long>> result = queryUsingBinding(sql, COUNT_TYPE_REF);
|
||||
|
||||
return extractCount(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count(KeyValueQuery<?> query, String keyspace) {
|
||||
Assert.notNull(query, "Query must not be null");
|
||||
Assert.hasText(keyspace, "Keyspace must not be empty");
|
||||
|
||||
Object criteria = query.getCriteria();
|
||||
|
||||
if (criteria == null) {
|
||||
return count(keyspace);
|
||||
}
|
||||
|
||||
String sql = createSql(COUNT_BY_FILTER_PATTERN, keyspace, criteria);
|
||||
List<List<Long>> result = queryUsingBinding(sql, COUNT_TYPE_REF);
|
||||
|
||||
return extractCount(result);
|
||||
}
|
||||
|
||||
private String getKeyspaceFilter(String keyspace) {
|
||||
return String.format("%s||%s-%%", stateStoreName, keyspace);
|
||||
}
|
||||
|
||||
private String createSql(String sqlPattern, String keyspace) {
|
||||
String keyspaceFilter = getKeyspaceFilter(keyspace);
|
||||
|
||||
return String.format(sqlPattern, keyspaceFilter);
|
||||
}
|
||||
|
||||
private String createSql(String sqlPattern, String keyspace, Object criteria) {
|
||||
String keyspaceFilter = getKeyspaceFilter(keyspace);
|
||||
|
||||
if (criteria instanceof DaprPredicate daprPredicate) {
|
||||
String path = daprPredicate.getPath().toString();
|
||||
String pathWithOutType = String.format("'%s'", path.substring(path.indexOf(".") + 1));
|
||||
String value = String.format("'%s'", daprPredicate.getValue().toString());
|
||||
|
||||
return String.format(sqlPattern, keyspaceFilter, pathWithOutType, value);
|
||||
} else if (criteria instanceof String) {
|
||||
SpelExpression expression = PARSER.parseRaw(criteria.toString());
|
||||
SpelNode leftNode = expression.getAST().getChild(0);
|
||||
SpelNode rightNode = expression.getAST().getChild(1);
|
||||
String left = String.format("'%s'", leftNode.toStringAST());
|
||||
String right = rightNode.toStringAST();
|
||||
|
||||
return String.format(sqlPattern, keyspaceFilter, left, right);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void execUsingBinding(String sql) {
|
||||
Map<String, String> meta = Collections.singletonMap("sql", sql);
|
||||
|
||||
daprClient.invokeBinding(bindingName, "exec", null, meta).block();
|
||||
}
|
||||
|
||||
private <T> T queryUsingBinding(String sql, TypeRef<T> typeRef) {
|
||||
Map<String, String> meta = Collections.singletonMap("sql", sql);
|
||||
|
||||
return daprClient.invokeBinding(bindingName, "query", null, meta, typeRef).block();
|
||||
}
|
||||
|
||||
private <T> Iterable<T> convertValues(List<List<Object>> values, Class<T> type) {
|
||||
if (values == null || values.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return values.stream()
|
||||
.flatMap(Collection::stream)
|
||||
.map(value -> convertValue(value, type))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private <T> T convertValue(Object value, Class<T> type) {
|
||||
try {
|
||||
return mapper.convertValue(value, type);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private long extractCount(List<List<Long>> values) {
|
||||
if (values == null || values.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return values.stream()
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toList())
|
||||
.get(0);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.spring.data.repository.config;
|
||||
|
||||
import org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
/**
|
||||
* Dapr specific {@link RepositoryBeanDefinitionRegistrarSupport} implementation.
|
||||
*/
|
||||
public class DaprRepositoriesRegistrar extends RepositoryBeanDefinitionRegistrarSupport {
|
||||
|
||||
@Override
|
||||
protected Class<? extends Annotation> getAnnotation() {
|
||||
return EnableDaprRepositories.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RepositoryConfigurationExtension getExtension() {
|
||||
return new DaprRepositoryConfigurationExtension();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.spring.data.repository.config;
|
||||
|
||||
import org.springframework.data.keyvalue.repository.config.KeyValueRepositoryConfigurationExtension;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
|
||||
|
||||
/**
|
||||
* {@link RepositoryConfigurationExtension} for Dapr-based repositories.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class DaprRepositoryConfigurationExtension extends KeyValueRepositoryConfigurationExtension {
|
||||
|
||||
@Override
|
||||
public String getModuleName() {
|
||||
return "Dapr";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getModulePrefix() {
|
||||
return "dapr";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDefaultKeyValueTemplateRef() {
|
||||
return "daprKeyValueTemplate";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.spring.data.repository.config;
|
||||
|
||||
import io.dapr.spring.data.repository.query.DaprPredicateQueryCreator;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.context.annotation.ComponentScan.Filter;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.keyvalue.core.KeyValueOperations;
|
||||
import org.springframework.data.keyvalue.repository.config.QueryCreatorType;
|
||||
import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactoryBean;
|
||||
import org.springframework.data.repository.config.DefaultRepositoryBaseClass;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Annotation to activate Dapr 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.
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@Import(DaprRepositoriesRegistrar.class)
|
||||
@QueryCreatorType(DaprPredicateQueryCreator.class)
|
||||
public @interface EnableDaprRepositories {
|
||||
|
||||
/**
|
||||
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations e.g.:
|
||||
* {@code @EnableJpaRepositories("org.my.pkg")} instead of {@code @EnableJpaRepositories(basePackages="org.my.pkg")}.
|
||||
*
|
||||
* @return alias of the base package
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @return array of base packages
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @return array of base classes
|
||||
*/
|
||||
Class<?>[] basePackageClasses() default {};
|
||||
|
||||
/**
|
||||
* Specifies which types are not eligible for component scanning.
|
||||
*
|
||||
* @return array of exclusion filters
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @return array of inclusion 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}.
|
||||
*
|
||||
* @return repository implementation post fix
|
||||
*/
|
||||
String repositoryImplementationPostfix() default "Impl";
|
||||
|
||||
/**
|
||||
* Configures the location of where to find the Spring Data named queries properties file.
|
||||
*
|
||||
* @return named queries location
|
||||
*/
|
||||
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}.
|
||||
*
|
||||
* @return key lookup strategy
|
||||
*/
|
||||
Key queryLookupStrategy() default Key.CREATE_IF_NOT_FOUND;
|
||||
|
||||
/**
|
||||
* Returns the {@link FactoryBean} class to be used for each repository instance. Defaults to
|
||||
* {@link KeyValueRepositoryFactoryBean}.
|
||||
*
|
||||
* @return repository factory bean class
|
||||
*/
|
||||
Class<?> repositoryFactoryBeanClass() default KeyValueRepositoryFactoryBean.class;
|
||||
|
||||
/**
|
||||
* Configure the repository base class to be used to create repository proxies for this particular configuration.
|
||||
*
|
||||
* @return repository base class
|
||||
*/
|
||||
Class<?> repositoryBaseClass() default DefaultRepositoryBaseClass.class;
|
||||
|
||||
/**
|
||||
* Configures the name of the {@link KeyValueOperations} bean to be used with the repositories detected.
|
||||
*
|
||||
* @return the Key value template bean name
|
||||
*/
|
||||
String keyValueTemplateRef() default "daprKeyValueTemplate";
|
||||
|
||||
/**
|
||||
* Configures whether nested repository-interfaces (e.g. defined as inner classes) should be discovered by the
|
||||
* repositories infrastructure.
|
||||
*
|
||||
* @return whether to consider nested repository interfaces
|
||||
*/
|
||||
boolean considerNestedRepositories() default false;
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package io.dapr.spring.data.repository.query;
|
||||
|
||||
import org.springframework.beans.BeanWrapper;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class DaprPredicate implements Predicate<Object> {
|
||||
|
||||
private final PropertyPath path;
|
||||
private final Function<Object, Boolean> check;
|
||||
private final Object value;
|
||||
|
||||
public DaprPredicate(PropertyPath path, Object expected) {
|
||||
this(path, expected, (valueToCompare) -> ObjectUtils.nullSafeEquals(valueToCompare, expected));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link DaprPredicate}.
|
||||
*
|
||||
* @param path The path to the property to compare.
|
||||
* @param value The value to compare.
|
||||
* @param check The function to check the value.
|
||||
*/
|
||||
public DaprPredicate(PropertyPath path, Object value, Function<Object, Boolean> check) {
|
||||
this.path = path;
|
||||
this.check = check;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public PropertyPath getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(Object o) {
|
||||
Object value = getValueByPath(o, path);
|
||||
return check.apply(value);
|
||||
}
|
||||
|
||||
private Object getValueByPath(Object root, PropertyPath path) {
|
||||
Object currentValue = root;
|
||||
|
||||
for (PropertyPath currentPath : path) {
|
||||
currentValue = wrap(currentValue).getPropertyValue(currentPath.getSegment());
|
||||
|
||||
if (currentValue == null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return currentValue;
|
||||
}
|
||||
|
||||
private BeanWrapper wrap(Object o) {
|
||||
return new DirectFieldAccessFallbackBeanWrapper(o);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.spring.data.repository.query;
|
||||
|
||||
import org.springframework.data.repository.query.parser.Part;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.comparator.Comparators;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
class DaprPredicateBuilder {
|
||||
|
||||
private final Part part;
|
||||
|
||||
private DaprPredicateBuilder(Part part) {
|
||||
this.part = part;
|
||||
}
|
||||
|
||||
static DaprPredicateBuilder propertyValueOf(Part part) {
|
||||
return new DaprPredicateBuilder(part);
|
||||
}
|
||||
|
||||
Predicate<Object> isTrue() {
|
||||
return new DaprPredicate(part.getProperty(), true);
|
||||
}
|
||||
|
||||
public Predicate<Object> isFalse() {
|
||||
return new DaprPredicate(part.getProperty(), false);
|
||||
}
|
||||
|
||||
public Predicate<Object> isEqualTo(Object value) {
|
||||
return new DaprPredicate(part.getProperty(), value, o -> {
|
||||
if (!ObjectUtils.nullSafeEquals(Part.IgnoreCaseType.NEVER, part.shouldIgnoreCase())) {
|
||||
if (o instanceof String s1 && value instanceof String s2) {
|
||||
return s1.equalsIgnoreCase(s2);
|
||||
}
|
||||
}
|
||||
|
||||
return ObjectUtils.nullSafeEquals(o, value);
|
||||
});
|
||||
}
|
||||
|
||||
public Predicate<Object> isNull() {
|
||||
return new DaprPredicate(part.getProperty(), null, Objects::isNull);
|
||||
}
|
||||
|
||||
public Predicate<Object> isNotNull() {
|
||||
return isNull().negate();
|
||||
}
|
||||
|
||||
public Predicate<Object> isLessThan(Object value) {
|
||||
return new DaprPredicate(part.getProperty(), value, o -> Comparators.nullsHigh().compare(o, value) < 0);
|
||||
}
|
||||
|
||||
public Predicate<Object> isLessThanEqual(Object value) {
|
||||
return new DaprPredicate(part.getProperty(), value, o -> Comparators.nullsHigh().compare(o, value) <= 0);
|
||||
}
|
||||
|
||||
public Predicate<Object> isGreaterThan(Object value) {
|
||||
return new DaprPredicate(part.getProperty(), value, o -> Comparators.nullsHigh().compare(o, value) > 0);
|
||||
}
|
||||
|
||||
public Predicate<Object> isGreaterThanEqual(Object value) {
|
||||
return new DaprPredicate(part.getProperty(), value, o -> Comparators.nullsHigh().compare(o, value) >= 0);
|
||||
}
|
||||
|
||||
public Predicate<Object> matches(Object value) {
|
||||
return new DaprPredicate(part.getProperty(), value, o -> {
|
||||
if (o == null || value == null) {
|
||||
return ObjectUtils.nullSafeEquals(o, value);
|
||||
}
|
||||
|
||||
if (value instanceof Pattern pattern) {
|
||||
return pattern.matcher(o.toString()).find();
|
||||
}
|
||||
|
||||
return o.toString().matches(value.toString());
|
||||
});
|
||||
}
|
||||
|
||||
public Predicate<Object> in(Object value) {
|
||||
return new DaprPredicate(part.getProperty(), value, o -> {
|
||||
if (value instanceof Collection<?> collection) {
|
||||
if (o instanceof Collection<?> subSet) {
|
||||
return collection.containsAll(subSet);
|
||||
}
|
||||
|
||||
return collection.contains(o);
|
||||
}
|
||||
|
||||
if (ObjectUtils.isArray(value)) {
|
||||
return ObjectUtils.containsElement(ObjectUtils.toObjectArray(value), value);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
public Predicate<Object> contains(Object value) {
|
||||
return new DaprPredicate(part.getProperty(), value, o -> {
|
||||
if (o == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (o instanceof Collection<?> collection) {
|
||||
return collection.contains(value);
|
||||
}
|
||||
|
||||
if (ObjectUtils.isArray(o)) {
|
||||
return ObjectUtils.containsElement(ObjectUtils.toObjectArray(o), value);
|
||||
}
|
||||
|
||||
if (o instanceof Map<?, ?> map) {
|
||||
return map.containsValue(value);
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String s = o.toString();
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(Part.IgnoreCaseType.NEVER, part.shouldIgnoreCase())) {
|
||||
return s.contains(value.toString());
|
||||
}
|
||||
|
||||
return s.toLowerCase().contains(value.toString().toLowerCase());
|
||||
});
|
||||
}
|
||||
|
||||
public Predicate<Object> startsWith(Object value) {
|
||||
return new DaprPredicate(part.getProperty(), value, o -> {
|
||||
if (!(o instanceof String s)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(Part.IgnoreCaseType.NEVER, part.shouldIgnoreCase())) {
|
||||
return s.startsWith(value.toString());
|
||||
}
|
||||
|
||||
return s.toLowerCase().startsWith(value.toString().toLowerCase());
|
||||
});
|
||||
}
|
||||
|
||||
public Predicate<Object> endsWith(Object value) {
|
||||
return new DaprPredicate(part.getProperty(), value, o -> {
|
||||
if (!(o instanceof String s)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(Part.IgnoreCaseType.NEVER, part.shouldIgnoreCase())) {
|
||||
return s.endsWith(value.toString());
|
||||
}
|
||||
|
||||
return s.toLowerCase().endsWith(value.toString().toLowerCase());
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.spring.data.repository.query;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
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.lang.Nullable;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* This class is copied from https://github.com/spring-projects/spring-data-keyvalue/blob/ff441439124585042dd0cbff952f977a343444d2/src/main/java/org/springframework/data/keyvalue/repository/query/PredicateQueryCreator.java#L46
|
||||
* because it has private accessors to internal classes, making it impossible to extend or use the original
|
||||
* This requires to be created from scratch to not use predicates, but this is only worth it if we can prove these
|
||||
* abstractions are worth the time.
|
||||
*/
|
||||
public class DaprPredicateQueryCreator extends AbstractQueryCreator<KeyValueQuery<Predicate<?>>, Predicate<?>> {
|
||||
|
||||
public DaprPredicateQueryCreator(PartTree tree, ParameterAccessor parameters) {
|
||||
super(tree, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Predicate<?> create(Part part, Iterator<Object> iterator) {
|
||||
DaprPredicateBuilder daprPredicateBuilder = DaprPredicateBuilder.propertyValueOf(part);
|
||||
|
||||
switch (part.getType()) {
|
||||
case TRUE:
|
||||
return daprPredicateBuilder.isTrue();
|
||||
case FALSE:
|
||||
return daprPredicateBuilder.isFalse();
|
||||
case SIMPLE_PROPERTY:
|
||||
return daprPredicateBuilder.isEqualTo(iterator.next());
|
||||
case IS_NULL:
|
||||
return daprPredicateBuilder.isNull();
|
||||
case IS_NOT_NULL:
|
||||
return daprPredicateBuilder.isNotNull();
|
||||
case LIKE:
|
||||
return daprPredicateBuilder.contains(iterator.next());
|
||||
case STARTING_WITH:
|
||||
return daprPredicateBuilder.startsWith(iterator.next());
|
||||
case AFTER:
|
||||
case GREATER_THAN:
|
||||
return daprPredicateBuilder.isGreaterThan(iterator.next());
|
||||
case GREATER_THAN_EQUAL:
|
||||
return daprPredicateBuilder.isGreaterThanEqual(iterator.next());
|
||||
case BEFORE:
|
||||
case LESS_THAN:
|
||||
return daprPredicateBuilder.isLessThan(iterator.next());
|
||||
case LESS_THAN_EQUAL:
|
||||
return daprPredicateBuilder.isLessThanEqual(iterator.next());
|
||||
case ENDING_WITH:
|
||||
return daprPredicateBuilder.endsWith(iterator.next());
|
||||
case BETWEEN:
|
||||
return daprPredicateBuilder.isGreaterThan(iterator.next())
|
||||
.and(daprPredicateBuilder.isLessThan(iterator.next()));
|
||||
case REGEX:
|
||||
return daprPredicateBuilder.matches(iterator.next());
|
||||
case IN:
|
||||
return daprPredicateBuilder.in(iterator.next());
|
||||
default:
|
||||
throw new InvalidDataAccessApiUsageException(String.format("Found invalid part '%s' in query", part.getType()));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Predicate<?> and(Part part, Predicate<?> base, Iterator<Object> iterator) {
|
||||
return base.and((Predicate) create(part, iterator));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Predicate<?> or(Predicate<?> base, Predicate<?> criteria) {
|
||||
return base.or((Predicate) criteria);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected KeyValueQuery<Predicate<?>> complete(@Nullable Predicate<?> criteria, Sort sort) {
|
||||
if (criteria == null) {
|
||||
return new KeyValueQuery<>(it -> true, sort);
|
||||
}
|
||||
return new KeyValueQuery<>(criteria, sort);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>io.dapr.spring</groupId>
|
||||
<artifactId>dapr-spring-parent</artifactId>
|
||||
<version>0.13.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>dapr-spring-messaging</artifactId>
|
||||
<name>dapr-spring-messaging</name>
|
||||
<description>Dapr Spring Messaging</description>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.spring.messaging;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface DaprMessagingOperations<T> {
|
||||
|
||||
/**
|
||||
* Sends a message to the specified topic in a blocking manner.
|
||||
*
|
||||
* @param topic the topic to send the message to or {@code null} to send to the
|
||||
* default topic
|
||||
* @param message the message to send
|
||||
*/
|
||||
void send(String topic, T message);
|
||||
|
||||
/**
|
||||
* Create a {@link SendMessageBuilder builder} for configuring and sending a message.
|
||||
*
|
||||
* @param message the payload of the message
|
||||
* @return the builder to configure and send the message
|
||||
*/
|
||||
SendMessageBuilder<T> newMessage(T message);
|
||||
|
||||
/**
|
||||
* Builder that can be used to configure and send a message. Provides more options
|
||||
* than the basic send/sendAsync methods provided by {@link DaprMessagingOperations}.
|
||||
*
|
||||
* @param <T> the message payload type
|
||||
*/
|
||||
interface SendMessageBuilder<T> {
|
||||
|
||||
/**
|
||||
* Specify the topic to send the message to.
|
||||
*
|
||||
* @param topic the destination topic
|
||||
* @return the current builder with the destination topic specified
|
||||
*/
|
||||
SendMessageBuilder<T> withTopic(String topic);
|
||||
|
||||
/**
|
||||
* Send the message in a blocking manner using the configured specification.
|
||||
*/
|
||||
void send();
|
||||
|
||||
/**
|
||||
* Uses the configured specification to send the message in a non-blocking manner.
|
||||
*
|
||||
* @return a Mono that completes when the message has been sent
|
||||
*/
|
||||
Mono<Void> sendAsync();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.spring.messaging;
|
||||
|
||||
import io.dapr.client.DaprClient;
|
||||
import io.dapr.client.domain.Metadata;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
public class DaprMessagingTemplate<T> implements DaprMessagingOperations<T> {
|
||||
|
||||
private static final String MESSAGE_TTL_IN_SECONDS = "10";
|
||||
|
||||
private final DaprClient daprClient;
|
||||
private final String pubsubName;
|
||||
|
||||
public DaprMessagingTemplate(DaprClient daprClient, String pubsubName) {
|
||||
this.daprClient = daprClient;
|
||||
this.pubsubName = pubsubName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(String topic, T message) {
|
||||
doSend(topic, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageBuilder<T> newMessage(T message) {
|
||||
return new SendMessageBuilderImpl<>(this, message);
|
||||
}
|
||||
|
||||
private void doSend(String topic, T message) {
|
||||
doSendAsync(topic, message).block();
|
||||
}
|
||||
|
||||
private Mono<Void> doSendAsync(String topic, T message) {
|
||||
return daprClient.publishEvent(pubsubName,
|
||||
topic,
|
||||
message,
|
||||
Collections.singletonMap(Metadata.TTL_IN_SECONDS, MESSAGE_TTL_IN_SECONDS));
|
||||
}
|
||||
|
||||
private static class SendMessageBuilderImpl<T> implements SendMessageBuilder<T> {
|
||||
|
||||
private final DaprMessagingTemplate<T> template;
|
||||
|
||||
private final T message;
|
||||
|
||||
private String topic;
|
||||
|
||||
SendMessageBuilderImpl(DaprMessagingTemplate<T> template, T message) {
|
||||
this.template = template;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SendMessageBuilder<T> withTopic(String topic) {
|
||||
this.topic = topic;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void send() {
|
||||
this.template.doSend(this.topic, this.message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> sendAsync() {
|
||||
return this.template.doSendAsync(this.topic, this.message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
<project
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
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>io.dapr</groupId>
|
||||
<artifactId>dapr-sdk-parent</artifactId>
|
||||
<version>1.13.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>io.dapr.spring</groupId>
|
||||
<artifactId>dapr-spring-parent</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>0.13.0-SNAPSHOT</version>
|
||||
<name>dapr-spring-parent</name>
|
||||
<description>SDK extension for Spring and Spring Boot</description>
|
||||
|
||||
<modules>
|
||||
<module>dapr-spring-core</module>
|
||||
<module>dapr-spring-data</module>
|
||||
<module>dapr-spring-messaging</module>
|
||||
<module>dapr-spring-boot-autoconfigure</module>
|
||||
<module>dapr-spring-boot-starters/dapr-spring-boot-starter</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<springboot.version>3.2.6</springboot.version>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<maven.compiler.release>17</maven.compiler.release>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${springboot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<!-- Dapr dependencies -->
|
||||
<dependency>
|
||||
<groupId>io.dapr</groupId>
|
||||
<artifactId>dapr-sdk</artifactId>
|
||||
<version>${dapr.sdk.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dapr</groupId>
|
||||
<artifactId>dapr-sdk-actors</artifactId>
|
||||
<version>${dapr.sdk.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.gmazzo.okhttp.mock</groupId>
|
||||
<artifactId>mock-client</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.3.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.7.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>0.8.12</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>default-prepare-agent</id>
|
||||
<goals>
|
||||
<goal>prepare-agent</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>report</id>
|
||||
<phase>test</phase>
|
||||
<goals>
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>target/jacoco-report/</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>check</id>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<rules>
|
||||
<rule>
|
||||
<element>BUNDLE</element>
|
||||
<includes>
|
||||
<include>io.dapr.springboot.DaprBeanPostProcessor</include>
|
||||
</includes>
|
||||
<limits>
|
||||
<limit>
|
||||
<counter>LINE</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>80%</minimum>
|
||||
</limit>
|
||||
</limits>
|
||||
</rule>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
|
@ -0,0 +1,11 @@
|
|||
<FindBugsFilter>
|
||||
<Match>
|
||||
<Package name="~io\.dapr\.spring.*"/>
|
||||
<Bug pattern="EI_EXPOSE_REP"/>
|
||||
</Match>
|
||||
|
||||
<Match>
|
||||
<Package name="~io\.dapr\.spring.*"/>
|
||||
<Bug pattern="EI_EXPOSE_REP2"/>
|
||||
</Match>
|
||||
</FindBugsFilter>
|
4
pom.xml
4
pom.xml
|
@ -1,6 +1,6 @@
|
|||
<project
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
|||
<protobuf.version>3.25.0</protobuf.version>
|
||||
<protocCommand>protoc</protocCommand>
|
||||
<dapr.proto.baseurl>https://raw.githubusercontent.com/dapr/dapr/v1.14.0-rc.2/dapr/proto</dapr.proto.baseurl>
|
||||
<dapr.sdk.version>1.13.0-SNAPSHOT</dapr.sdk.version>
|
||||
<dapr.sdk.alpha.version>0.13.0-SNAPSHOT</dapr.sdk.alpha.version>
|
||||
<os-maven-plugin.version>1.7.1</os-maven-plugin.version>
|
||||
<maven-dependency-plugin.version>3.1.1</maven-dependency-plugin.version>
|
||||
|
@ -323,6 +324,7 @@
|
|||
<module>sdk-actors</module>
|
||||
<module>sdk-workflows</module>
|
||||
<module>sdk-springboot</module>
|
||||
<module>dapr-spring</module>
|
||||
<module>examples</module>
|
||||
<!-- We are following test containers artifact convention on purpose, don't rename -->
|
||||
<module>testcontainers-dapr</module>
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
<springboot.version>3.3.1</springboot.version>
|
||||
<logback-classic.version>1.4.12</logback-classic.version>
|
||||
<wiremock.version>3.9.1</wiremock.version>
|
||||
<testcontainers-dapr.version>${dapr.sdk.alpha.version}</testcontainers-dapr.version>
|
||||
<testcontainers-test.version>1.20.0</testcontainers-test.version>
|
||||
</properties>
|
||||
|
||||
|
@ -137,14 +136,14 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dapr</groupId>
|
||||
<artifactId>testcontainers-dapr</artifactId>
|
||||
<version>${dapr.sdk.alpha.version}</version>
|
||||
<artifactId>dapr-sdk-actors</artifactId>
|
||||
<version>${dapr.sdk.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dapr</groupId>
|
||||
<artifactId>dapr-sdk-actors</artifactId>
|
||||
<version>${dapr.sdk.version}</version>
|
||||
<artifactId>dapr-sdk-workflows</artifactId>
|
||||
<version>${dapr.sdk.alpha.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -153,11 +152,44 @@
|
|||
<version>${dapr.sdk.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dapr.spring</groupId>
|
||||
<artifactId>dapr-spring-core</artifactId>
|
||||
<version>${dapr.sdk.alpha.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dapr.spring</groupId>
|
||||
<artifactId>dapr-spring-data</artifactId>
|
||||
<version>${dapr.sdk.alpha.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dapr.spring</groupId>
|
||||
<artifactId>dapr-spring-messaging</artifactId>
|
||||
<version>${dapr.sdk.alpha.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dapr.spring</groupId>
|
||||
<artifactId>dapr-spring-boot-autoconfigure</artifactId>
|
||||
<version>${dapr.sdk.alpha.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-keyvalue</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-testcontainers</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.wiremock</groupId>
|
||||
<artifactId>wiremock-standalone</artifactId>
|
||||
|
@ -187,6 +219,29 @@
|
|||
<version>3.9</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dapr</groupId>
|
||||
<artifactId>testcontainers-dapr</artifactId>
|
||||
<version>${dapr.sdk.alpha.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${testcontainers-test.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>${testcontainers-test.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>mysql</artifactId>
|
||||
<version>${testcontainers-test.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.annotation</groupId>
|
||||
<artifactId>jakarta.annotation-api</artifactId>
|
||||
|
@ -216,17 +271,6 @@
|
|||
<version>4.0.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dapr</groupId>
|
||||
<artifactId>testcontainers-dapr</artifactId>
|
||||
<version>${testcontainers-dapr.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${testcontainers-test.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -88,11 +88,12 @@ public class MethodInvokeIT extends BaseIT {
|
|||
MethodInvokeServiceGrpc.MethodInvokeServiceBlockingStub stub = createGrpcStub(client);
|
||||
long started = System.currentTimeMillis();
|
||||
SleepRequest req = SleepRequest.newBuilder().setSeconds(1).build();
|
||||
String message = assertThrows(StatusRuntimeException.class, () -> stub.sleep(req)).getMessage();
|
||||
StatusRuntimeException exception = assertThrows(StatusRuntimeException.class, () -> stub.sleep(req));
|
||||
long delay = System.currentTimeMillis() - started;
|
||||
Status.Code code = exception.getStatus().getCode();
|
||||
|
||||
assertTrue(delay >= TIMEOUT_MS, "Delay: " + delay + " is not greater than timeout: " + TIMEOUT_MS);
|
||||
assertTrue(message.contains("DEADLINE_EXCEEDED"), "The message contains DEADLINE_EXCEEDED: " + message);
|
||||
assertTrue(message.contains("CallOptions deadline exceeded after"), "The message contains DEADLINE_EXCEEDED: " + message);
|
||||
assertEquals(Status.DEADLINE_EXCEEDED.getCode(), code, "Expected timeout error");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -117,8 +117,10 @@ public class MethodInvokeIT extends BaseIT {
|
|||
client.invokeMethod(daprRun.getAppName(), "sleep", 1, HttpExtension.POST)
|
||||
.block(Duration.ofMillis(10));
|
||||
}).getMessage();
|
||||
|
||||
long delay = System.currentTimeMillis() - started;
|
||||
assertTrue(delay <= 200); // 200 ms is a reasonable delay if the request timed out.
|
||||
|
||||
assertTrue(delay <= 200, "Delay: " + delay + " is not less than timeout: 200");
|
||||
assertEquals("Timeout on blocking read for 10000000 NANOSECONDS", message);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package io.dapr.it.spring.data;
|
||||
|
||||
import org.testcontainers.containers.MySQLContainer;
|
||||
import org.testcontainers.utility.DockerImageName;
|
||||
|
||||
public class CustomMySQLContainer<SELF extends MySQLContainer<SELF>> extends MySQLContainer<SELF> {
|
||||
|
||||
public CustomMySQLContainer(String dockerImageName) {
|
||||
super(DockerImageName.parse(dockerImageName));
|
||||
}
|
||||
|
||||
protected void waitUntilContainerStarted() {
|
||||
this.getWaitStrategy().waitUntilReady(this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.it.spring.data;
|
||||
|
||||
import io.dapr.testcontainers.Component;
|
||||
import io.dapr.testcontainers.DaprContainer;
|
||||
import io.dapr.testcontainers.DaprLogLevel;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||
import org.springframework.test.context.DynamicPropertySource;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.containers.PostgreSQLContainer;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static io.dapr.it.spring.data.DaprSpringDataConstants.BINDING_NAME;
|
||||
import static io.dapr.it.spring.data.DaprSpringDataConstants.STATE_STORE_NAME;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link DaprKeyValueRepositoryIT}.
|
||||
*/
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = TestDaprSpringDataConfiguration.class)
|
||||
@Testcontainers
|
||||
@Tag("testcontainers")
|
||||
public class DaprKeyValueRepositoryIT {
|
||||
private static final String CONNECTION_STRING =
|
||||
"host=postgres-repository user=postgres password=password port=5432 connect_timeout=10 database=dapr_db_repository";
|
||||
private static final Map<String, String> STATE_STORE_PROPERTIES = createStateStoreProperties();
|
||||
|
||||
private static final Map<String, String> BINDING_PROPERTIES = Collections.singletonMap("connectionString", CONNECTION_STRING);
|
||||
|
||||
private static final Network DAPR_NETWORK = Network.newNetwork();
|
||||
|
||||
@Container
|
||||
private static final PostgreSQLContainer<?> POSTGRE_SQL_CONTAINER = new PostgreSQLContainer<>("postgres:16-alpine")
|
||||
.withNetworkAliases("postgres-repository")
|
||||
.withDatabaseName("dapr_db_repository")
|
||||
.withUsername("postgres")
|
||||
.withPassword("password")
|
||||
.withNetwork(DAPR_NETWORK);
|
||||
|
||||
@Container
|
||||
private static final DaprContainer DAPR_CONTAINER = new DaprContainer("daprio/daprd:1.13.2")
|
||||
.withAppName("postgresql-repository-dapr-app")
|
||||
.withNetwork(DAPR_NETWORK)
|
||||
.withComponent(new Component(STATE_STORE_NAME, "state.postgresql", "v1", STATE_STORE_PROPERTIES))
|
||||
.withComponent(new Component(BINDING_NAME, "bindings.postgresql", "v1", BINDING_PROPERTIES))
|
||||
.withDaprLogLevel(DaprLogLevel.DEBUG)
|
||||
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
|
||||
.dependsOn(POSTGRE_SQL_CONTAINER);
|
||||
|
||||
@DynamicPropertySource
|
||||
static void daprProperties(DynamicPropertyRegistry registry) {
|
||||
registry.add("dapr.http.endpoint", DAPR_CONTAINER::getHttpEndpoint);
|
||||
registry.add("dapr.grpc.endpoint", DAPR_CONTAINER::getGrpcEndpoint);
|
||||
registry.add("dapr.grpc.port", DAPR_CONTAINER::getGrpcPort);
|
||||
registry.add("dapr.http.port", DAPR_CONTAINER::getHttpPort);
|
||||
}
|
||||
|
||||
private static Map<String, String> createStateStoreProperties() {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
|
||||
result.put("keyPrefix", "name");
|
||||
result.put("actorStateStore", String.valueOf(true));
|
||||
result.put("connectionString", CONNECTION_STRING);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private TestTypeRepository repository;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
repository.deleteAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindById() {
|
||||
TestType saved = repository.save(new TestType(3, "test"));
|
||||
TestType byId = repository.findById(3).get();
|
||||
|
||||
assertEquals(saved, byId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExistsById() {
|
||||
repository.save(new TestType(3, "test"));
|
||||
|
||||
boolean existsById = repository.existsById(3);
|
||||
assertTrue(existsById);
|
||||
|
||||
boolean existsById2 = repository.existsById(4);
|
||||
assertFalse(existsById2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindAll() {
|
||||
repository.save(new TestType(3, "test"));
|
||||
repository.save(new TestType(4, "test2"));
|
||||
|
||||
Iterable<TestType> all = repository.findAll();
|
||||
|
||||
assertEquals(2, all.spliterator().getExactSizeIfKnown());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinUsingQuery() {
|
||||
repository.save(new TestType(3, "test"));
|
||||
repository.save(new TestType(4, "test2"));
|
||||
|
||||
List<TestType> byContent = repository.findByContent("test2");
|
||||
|
||||
assertEquals(1, byContent.size());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package io.dapr.it.spring.data;
|
||||
|
||||
public class DaprSpringDataConstants {
|
||||
private DaprSpringDataConstants() {
|
||||
}
|
||||
|
||||
public static final String STATE_STORE_NAME = "kvstore";
|
||||
public static final String BINDING_NAME = "kvbinding";
|
||||
}
|
|
@ -0,0 +1,242 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.it.spring.data;
|
||||
|
||||
import io.dapr.client.DaprClient;
|
||||
import io.dapr.spring.data.DaprKeyValueTemplate;
|
||||
import io.dapr.testcontainers.Component;
|
||||
import io.dapr.testcontainers.DaprContainer;
|
||||
import io.dapr.testcontainers.DaprLogLevel;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||
import org.springframework.test.context.DynamicPropertySource;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.testcontainers.containers.MySQLContainer;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
import org.testcontainers.containers.wait.strategy.WaitStrategy;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static io.dapr.it.spring.data.DaprSpringDataConstants.STATE_STORE_NAME;
|
||||
import static io.dapr.it.spring.data.DaprSpringDataConstants.BINDING_NAME;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link MySQLDaprKeyValueTemplateIT}.
|
||||
*/
|
||||
@SuppressWarnings("AbbreviationAsWordInName")
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = TestDaprSpringDataConfiguration.class)
|
||||
@Testcontainers
|
||||
@Tag("testcontainers")
|
||||
public class MySQLDaprKeyValueTemplateIT {
|
||||
private static final String STATE_STORE_DSN = "mysql:password@tcp(mysql:3306)/";
|
||||
private static final String BINDING_DSN = "mysql:password@tcp(mysql:3306)/dapr_db";
|
||||
private static final Map<String, String> STATE_STORE_PROPERTIES = createStateStoreProperties();
|
||||
|
||||
private static final Map<String, String> BINDING_PROPERTIES = Collections.singletonMap("url", BINDING_DSN);
|
||||
|
||||
private static final Network DAPR_NETWORK = Network.newNetwork();
|
||||
|
||||
private static final WaitStrategy MYSQL_WAIT_STRATEGY = Wait
|
||||
.forLogMessage(".*port: 3306 MySQL Community Server \\(GPL\\).*", 1)
|
||||
.withStartupTimeout(Duration.of(60, ChronoUnit.SECONDS));
|
||||
|
||||
@Container
|
||||
private static final MySQLContainer<?> MY_SQL_CONTAINER = new CustomMySQLContainer<>("mysql:5.7.34")
|
||||
.withNetworkAliases("mysql")
|
||||
.withDatabaseName("dapr_db")
|
||||
.withUsername("mysql")
|
||||
.withPassword("password")
|
||||
.withNetwork(DAPR_NETWORK)
|
||||
.waitingFor(MYSQL_WAIT_STRATEGY);
|
||||
|
||||
@Container
|
||||
private static final DaprContainer DAPR_CONTAINER = new DaprContainer("daprio/daprd:1.13.2")
|
||||
.withAppName("mysql-dapr-app")
|
||||
.withNetwork(DAPR_NETWORK)
|
||||
.withComponent(new Component(STATE_STORE_NAME, "state.mysql", "v1", STATE_STORE_PROPERTIES))
|
||||
.withComponent(new Component(BINDING_NAME, "bindings.mysql", "v1", BINDING_PROPERTIES))
|
||||
.withDaprLogLevel(DaprLogLevel.DEBUG)
|
||||
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
|
||||
.dependsOn(MY_SQL_CONTAINER);
|
||||
|
||||
@DynamicPropertySource
|
||||
static void daprProperties(DynamicPropertyRegistry registry) {
|
||||
registry.add("dapr.http.endpoint", DAPR_CONTAINER::getHttpEndpoint);
|
||||
registry.add("dapr.grpc.endpoint", DAPR_CONTAINER::getGrpcEndpoint);
|
||||
registry.add("dapr.grpc.port", DAPR_CONTAINER::getGrpcPort);
|
||||
registry.add("dapr.http.port", DAPR_CONTAINER::getHttpPort);
|
||||
}
|
||||
|
||||
private static Map<String, String> createStateStoreProperties() {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
|
||||
result.put("keyPrefix", "name");
|
||||
result.put("schemaName", "dapr_db");
|
||||
result.put("actorStateStore", "true");
|
||||
result.put("connectionString", STATE_STORE_DSN);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private DaprClient daprClient;
|
||||
|
||||
@Autowired
|
||||
private DaprKeyValueTemplate keyValueTemplate;
|
||||
|
||||
/**
|
||||
* Cleans up the state store after each test.
|
||||
*/
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
var meta = Collections.singletonMap("sql", "delete from state");
|
||||
|
||||
daprClient.invokeBinding(BINDING_NAME, "exec", null, meta).block();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertAndQueryDaprKeyValueTemplate() {
|
||||
int itemId = 3;
|
||||
TestType savedType = keyValueTemplate.insert(new TestType(itemId, "test"));
|
||||
assertThat(savedType).isNotNull();
|
||||
|
||||
Optional<TestType> findById = keyValueTemplate.findById(itemId, TestType.class);
|
||||
assertThat(findById.isEmpty()).isFalse();
|
||||
assertThat(findById.get()).isEqualTo(savedType);
|
||||
|
||||
KeyValueQuery<String> keyValueQuery = new KeyValueQuery<>("content == 'test'");
|
||||
|
||||
Iterable<TestType> myTypes = keyValueTemplate.find(keyValueQuery, TestType.class);
|
||||
assertThat(myTypes.iterator().hasNext()).isTrue();
|
||||
|
||||
TestType item = myTypes.iterator().next();
|
||||
assertThat(item.getId()).isEqualTo(Integer.valueOf(itemId));
|
||||
assertThat(item.getContent()).isEqualTo("test");
|
||||
|
||||
keyValueQuery = new KeyValueQuery<>("content == 'asd'");
|
||||
|
||||
myTypes = keyValueTemplate.find(keyValueQuery, TestType.class);
|
||||
assertThat(!myTypes.iterator().hasNext()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertMoreThan10AndQueryDaprKeyValueTemplate() {
|
||||
int count = 10;
|
||||
List<TestType> items = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
items.add(keyValueTemplate.insert(new TestType(i, "test")));
|
||||
}
|
||||
|
||||
KeyValueQuery<String> keyValueQuery = new KeyValueQuery<>("content == 'test'");
|
||||
keyValueQuery.setRows(100);
|
||||
keyValueQuery.setOffset(0);
|
||||
|
||||
Iterable<TestType> foundItems = keyValueTemplate.find(keyValueQuery, TestType.class);
|
||||
assertThat(foundItems.iterator().hasNext()).isTrue();
|
||||
|
||||
int index = 0;
|
||||
|
||||
for (TestType foundItem : foundItems) {
|
||||
TestType item = items.get(index);
|
||||
|
||||
assertEquals(item.getId(), foundItem.getId());
|
||||
assertEquals(item.getContent(), foundItem.getContent());
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
assertEquals(index, items.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateDaprKeyValueTemplate() {
|
||||
int itemId = 2;
|
||||
TestType insertedType = keyValueTemplate.insert(new TestType(itemId, "test"));
|
||||
assertThat(insertedType).isNotNull();
|
||||
|
||||
TestType updatedType = keyValueTemplate.update(new TestType(itemId, "test2"));
|
||||
assertThat(updatedType).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteAllOfDaprKeyValueTemplate() {
|
||||
int itemId = 1;
|
||||
TestType insertedType = keyValueTemplate.insert(new TestType(itemId, "test"));
|
||||
assertThat(insertedType).isNotNull();
|
||||
|
||||
keyValueTemplate.delete(TestType.class);
|
||||
|
||||
Optional<TestType> result = keyValueTemplate.findById(itemId, TestType.class);
|
||||
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAllOfDaprKeyValueTemplate() {
|
||||
int itemId = 1;
|
||||
TestType insertedType = keyValueTemplate.insert(new TestType(itemId, "test"));
|
||||
assertThat(insertedType).isNotNull();
|
||||
|
||||
Iterable<TestType> result = keyValueTemplate.findAll(TestType.class);
|
||||
|
||||
assertThat(result.iterator().hasNext()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCountDaprKeyValueTemplate() {
|
||||
int itemId = 1;
|
||||
TestType insertedType = keyValueTemplate.insert(new TestType(itemId, "test"));
|
||||
assertThat(insertedType).isNotNull();
|
||||
|
||||
long result = keyValueTemplate.count(TestType.class);
|
||||
|
||||
assertThat(result).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCountWithQueryDaprKeyValueTemplate() {
|
||||
int itemId = 1;
|
||||
TestType insertedType = keyValueTemplate.insert(new TestType(itemId, "test"));
|
||||
assertThat(insertedType).isNotNull();
|
||||
|
||||
KeyValueQuery<String> keyValueQuery = new KeyValueQuery<>("content == 'test'");
|
||||
keyValueQuery.setRows(100);
|
||||
keyValueQuery.setOffset(0);
|
||||
|
||||
long result = keyValueTemplate.count(keyValueQuery, TestType.class);
|
||||
|
||||
assertThat(result).isEqualTo(1);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.it.spring.data;
|
||||
|
||||
import io.dapr.client.DaprClient;
|
||||
import io.dapr.spring.data.DaprKeyValueTemplate;
|
||||
import io.dapr.testcontainers.Component;
|
||||
import io.dapr.testcontainers.DaprContainer;
|
||||
import io.dapr.testcontainers.DaprLogLevel;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||
import org.springframework.test.context.DynamicPropertySource;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.containers.PostgreSQLContainer;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static io.dapr.it.spring.data.DaprSpringDataConstants.BINDING_NAME;
|
||||
import static io.dapr.it.spring.data.DaprSpringDataConstants.STATE_STORE_NAME;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link PostgreSQLDaprKeyValueTemplateIT}.
|
||||
*/
|
||||
@SuppressWarnings("AbbreviationAsWordInName")
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = TestDaprSpringDataConfiguration.class)
|
||||
@Testcontainers
|
||||
@Tag("testcontainers")
|
||||
public class PostgreSQLDaprKeyValueTemplateIT {
|
||||
private static final String CONNECTION_STRING =
|
||||
"host=postgres user=postgres password=password port=5432 connect_timeout=10 database=dapr_db";
|
||||
private static final Map<String, String> STATE_STORE_PROPERTIES = createStateStoreProperties();
|
||||
|
||||
private static final Map<String, String> BINDING_PROPERTIES = Collections.singletonMap("connectionString", CONNECTION_STRING);
|
||||
|
||||
private static final Network DAPR_NETWORK = Network.newNetwork();
|
||||
|
||||
@Container
|
||||
private static final PostgreSQLContainer<?> POSTGRE_SQL_CONTAINER = new PostgreSQLContainer<>("postgres:16-alpine")
|
||||
.withNetworkAliases("postgres")
|
||||
.withDatabaseName("dapr_db")
|
||||
.withUsername("postgres")
|
||||
.withPassword("password")
|
||||
.withNetwork(DAPR_NETWORK);
|
||||
|
||||
@Container
|
||||
private static final DaprContainer DAPR_CONTAINER = new DaprContainer("daprio/daprd:1.13.2")
|
||||
.withAppName("postgresql-dapr-app")
|
||||
.withNetwork(DAPR_NETWORK)
|
||||
.withComponent(new Component(STATE_STORE_NAME, "state.postgresql", "v1", STATE_STORE_PROPERTIES))
|
||||
.withComponent(new Component(BINDING_NAME, "bindings.postgresql", "v1", BINDING_PROPERTIES))
|
||||
.withDaprLogLevel(DaprLogLevel.DEBUG)
|
||||
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
|
||||
.dependsOn(POSTGRE_SQL_CONTAINER);
|
||||
|
||||
@DynamicPropertySource
|
||||
static void daprProperties(DynamicPropertyRegistry registry) {
|
||||
registry.add("dapr.http.endpoint", DAPR_CONTAINER::getHttpEndpoint);
|
||||
registry.add("dapr.grpc.endpoint", DAPR_CONTAINER::getGrpcEndpoint);
|
||||
registry.add("dapr.grpc.port", DAPR_CONTAINER::getGrpcPort);
|
||||
registry.add("dapr.http.port", DAPR_CONTAINER::getHttpPort);
|
||||
}
|
||||
|
||||
private static Map<String, String> createStateStoreProperties() {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
|
||||
result.put("keyPrefix", "name");
|
||||
result.put("actorStateStore", String.valueOf(true));
|
||||
result.put("connectionString", CONNECTION_STRING);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private DaprClient daprClient;
|
||||
|
||||
@Autowired
|
||||
private DaprKeyValueTemplate keyValueTemplate;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
var meta = Collections.singletonMap("sql", "delete from state");
|
||||
|
||||
daprClient.invokeBinding(BINDING_NAME, "exec", null, meta).block();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertAndQueryDaprKeyValueTemplate() {
|
||||
int itemId = 3;
|
||||
TestType savedType = keyValueTemplate.insert(new TestType(itemId, "test"));
|
||||
assertThat(savedType).isNotNull();
|
||||
|
||||
Optional<TestType> findById = keyValueTemplate.findById(itemId, TestType.class);
|
||||
assertThat(findById.isEmpty()).isFalse();
|
||||
assertThat(findById.get()).isEqualTo(savedType);
|
||||
|
||||
KeyValueQuery<String> keyValueQuery = new KeyValueQuery<>("content == 'test'");
|
||||
|
||||
Iterable<TestType> myTypes = keyValueTemplate.find(keyValueQuery, TestType.class);
|
||||
assertThat(myTypes.iterator().hasNext()).isTrue();
|
||||
|
||||
TestType item = myTypes.iterator().next();
|
||||
assertThat(item.getId()).isEqualTo(Integer.valueOf(itemId));
|
||||
assertThat(item.getContent()).isEqualTo("test");
|
||||
|
||||
keyValueQuery = new KeyValueQuery<>("content == 'asd'");
|
||||
|
||||
myTypes = keyValueTemplate.find(keyValueQuery, TestType.class);
|
||||
assertThat(!myTypes.iterator().hasNext()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertMoreThan10AndQueryDaprKeyValueTemplate() {
|
||||
int count = 10;
|
||||
List<TestType> items = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
items.add(keyValueTemplate.insert(new TestType(i, "test")));
|
||||
}
|
||||
|
||||
KeyValueQuery<String> keyValueQuery = new KeyValueQuery<>("content == 'test'");
|
||||
keyValueQuery.setRows(100);
|
||||
keyValueQuery.setOffset(0);
|
||||
|
||||
Iterable<TestType> foundItems = keyValueTemplate.find(keyValueQuery, TestType.class);
|
||||
assertThat(foundItems.iterator().hasNext()).isTrue();
|
||||
|
||||
int index = 0;
|
||||
|
||||
for (TestType foundItem : foundItems) {
|
||||
TestType item = items.get(index);
|
||||
|
||||
assertEquals(item.getId(), foundItem.getId());
|
||||
assertEquals(item.getContent(), foundItem.getContent());
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
assertEquals(index, items.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateDaprKeyValueTemplate() {
|
||||
int itemId = 2;
|
||||
TestType insertedType = keyValueTemplate.insert(new TestType(itemId, "test"));
|
||||
assertThat(insertedType).isNotNull();
|
||||
|
||||
TestType updatedType = keyValueTemplate.update(new TestType(itemId, "test2"));
|
||||
assertThat(updatedType).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteAllOfDaprKeyValueTemplate() {
|
||||
int itemId = 1;
|
||||
TestType insertedType = keyValueTemplate.insert(new TestType(itemId, "test"));
|
||||
assertThat(insertedType).isNotNull();
|
||||
|
||||
keyValueTemplate.delete(TestType.class);
|
||||
|
||||
Optional<TestType> result = keyValueTemplate.findById(itemId, TestType.class);
|
||||
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAllOfDaprKeyValueTemplate() {
|
||||
int itemId = 1;
|
||||
TestType insertedType = keyValueTemplate.insert(new TestType(itemId, "test"));
|
||||
assertThat(insertedType).isNotNull();
|
||||
|
||||
Iterable<TestType> result = keyValueTemplate.findAll(TestType.class);
|
||||
|
||||
assertThat(result.iterator().hasNext()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCountDaprKeyValueTemplate() {
|
||||
int itemId = 1;
|
||||
TestType insertedType = keyValueTemplate.insert(new TestType(itemId, "test"));
|
||||
assertThat(insertedType).isNotNull();
|
||||
|
||||
long result = keyValueTemplate.count(TestType.class);
|
||||
|
||||
assertThat(result).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCountWithQueryDaprKeyValueTemplate() {
|
||||
int itemId = 1;
|
||||
TestType insertedType = keyValueTemplate.insert(new TestType(itemId, "test"));
|
||||
assertThat(insertedType).isNotNull();
|
||||
|
||||
KeyValueQuery<String> keyValueQuery = new KeyValueQuery<>("content == 'test'");
|
||||
keyValueQuery.setRows(100);
|
||||
keyValueQuery.setOffset(0);
|
||||
|
||||
long result = keyValueTemplate.count(keyValueQuery, TestType.class);
|
||||
|
||||
assertThat(result).isEqualTo(1);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package io.dapr.it.spring.data;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.dapr.client.DaprClient;
|
||||
import io.dapr.spring.boot.autoconfigure.client.DaprClientAutoConfiguration;
|
||||
import io.dapr.spring.core.client.DaprClientCustomizer;
|
||||
import io.dapr.spring.data.DaprKeyValueAdapterResolver;
|
||||
import io.dapr.spring.data.DaprKeyValueTemplate;
|
||||
import io.dapr.spring.data.KeyValueAdapterResolver;
|
||||
import io.dapr.spring.data.repository.config.EnableDaprRepositories;
|
||||
import io.dapr.testcontainers.TestcontainersDaprClientCustomizer;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
@Configuration
|
||||
@EnableDaprRepositories
|
||||
@Import(DaprClientAutoConfiguration.class)
|
||||
public class TestDaprSpringDataConfiguration {
|
||||
@Bean
|
||||
public ObjectMapper mapper() {
|
||||
return new ObjectMapper();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DaprClientCustomizer daprClientCustomizer(
|
||||
@Value("${dapr.http.endpoint}") String daprHttpEndpoint,
|
||||
@Value("${dapr.grpc.endpoint}") String daprGrpcEndpoint,
|
||||
@Value("${dapr.http.port}") String daprHttpPort,
|
||||
@Value("${dapr.grpc.port}") String daprGrpcPort
|
||||
){
|
||||
return new TestcontainersDaprClientCustomizer(daprHttpEndpoint, daprGrpcEndpoint, daprHttpPort, daprGrpcPort);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public KeyValueAdapterResolver keyValueAdapterResolver(DaprClient daprClient, ObjectMapper mapper) {
|
||||
String storeName = DaprSpringDataConstants.STATE_STORE_NAME;
|
||||
String bindingName = DaprSpringDataConstants.BINDING_NAME;
|
||||
|
||||
return new DaprKeyValueAdapterResolver(daprClient, mapper, storeName, bindingName);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DaprKeyValueTemplate daprKeyValueTemplate(KeyValueAdapterResolver keyValueAdapterResolver) {
|
||||
return new DaprKeyValueTemplate(keyValueAdapterResolver);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.it.spring.data;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class TestType {
|
||||
|
||||
@Id
|
||||
private Integer id;
|
||||
private String content;
|
||||
|
||||
public TestType() {
|
||||
}
|
||||
|
||||
public TestType(Integer id, String content) {
|
||||
this.id = id;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
TestType testType = (TestType) o;
|
||||
return Objects.equals(id, testType.id) && Objects.equals(content, testType.content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, content);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.it.spring.data;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface TestTypeRepository extends CrudRepository<TestType, Integer> {
|
||||
List<TestType> findByContent(String content);
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.it.spring.messaging;
|
||||
|
||||
import io.dapr.client.domain.CloudEvent;
|
||||
import io.dapr.spring.boot.autoconfigure.client.DaprClientAutoConfiguration;
|
||||
import io.dapr.spring.messaging.DaprMessagingTemplate;
|
||||
import io.dapr.testcontainers.Component;
|
||||
import io.dapr.testcontainers.DaprContainer;
|
||||
import io.dapr.testcontainers.DaprLogLevel;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||
import org.springframework.test.context.DynamicPropertySource;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest(
|
||||
webEnvironment = WebEnvironment.DEFINED_PORT,
|
||||
classes = {
|
||||
DaprClientAutoConfiguration.class,
|
||||
TestApplication.class
|
||||
},
|
||||
properties = {"dapr.pubsub.name=pubsub"}
|
||||
)
|
||||
@Testcontainers
|
||||
@Tag("testcontainers")
|
||||
public class DaprSpringMessagingIT {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DaprSpringMessagingIT.class);
|
||||
|
||||
private static final String TOPIC = "mockTopic";
|
||||
|
||||
private static final Network DAPR_NETWORK = Network.newNetwork();
|
||||
|
||||
@Container
|
||||
private static final DaprContainer DAPR_CONTAINER = new DaprContainer("daprio/daprd:1.13.2")
|
||||
.withAppName("messaging-dapr-app")
|
||||
.withNetwork(DAPR_NETWORK)
|
||||
.withComponent(new Component("pubsub", "pubsub.in-memory", "v1", Collections.emptyMap()))
|
||||
.withAppPort(8080)
|
||||
.withDaprLogLevel(DaprLogLevel.DEBUG)
|
||||
.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
|
||||
.withAppChannelAddress("host.testcontainers.internal");
|
||||
|
||||
@DynamicPropertySource
|
||||
static void daprProperties(DynamicPropertyRegistry registry) {
|
||||
org.testcontainers.Testcontainers.exposeHostPorts(8080);
|
||||
|
||||
registry.add("dapr.http.endpoint", DAPR_CONTAINER::getHttpEndpoint);
|
||||
registry.add("dapr.grpc.endpoint", DAPR_CONTAINER::getGrpcEndpoint);
|
||||
registry.add("dapr.grpc.port", DAPR_CONTAINER::getGrpcPort);
|
||||
registry.add("dapr.http.port", DAPR_CONTAINER::getHttpPort);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private DaprMessagingTemplate<String> messagingTemplate;
|
||||
|
||||
@Autowired
|
||||
private TestRestController testRestController;
|
||||
|
||||
@Test
|
||||
public void testDaprMessagingTemplate() throws InterruptedException {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
var msg = "ProduceAndReadWithPrimitiveMessageType:" + i;
|
||||
|
||||
messagingTemplate.send(TOPIC, msg);
|
||||
|
||||
logger.info("++++++PRODUCE {}------", msg);
|
||||
}
|
||||
|
||||
// Wait for the messages to arrive
|
||||
Thread.sleep(1000);
|
||||
|
||||
List<CloudEvent<String>> events = testRestController.getEvents();
|
||||
|
||||
assertThat(events.size()).isEqualTo(10);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.it.spring.messaging;
|
||||
|
||||
import io.dapr.client.DaprClient;
|
||||
import io.dapr.spring.boot.autoconfigure.pubsub.DaprPubSubProperties;
|
||||
import io.dapr.spring.core.client.DaprClientCustomizer;
|
||||
import io.dapr.spring.messaging.DaprMessagingTemplate;
|
||||
import io.dapr.testcontainers.TestcontainersDaprClientCustomizer;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@SpringBootApplication
|
||||
public class TestApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(TestApplication.class, args);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(DaprPubSubProperties.class)
|
||||
static class DaprSpringMessagingConfiguration {
|
||||
|
||||
@Bean
|
||||
public DaprClientCustomizer daprClientCustomizer(
|
||||
@Value("${dapr.http.endpoint}") String daprHttpEndpoint,
|
||||
@Value("${dapr.grpc.endpoint}") String daprGrpcEndpoint,
|
||||
@Value("${dapr.http.port}") String daprHttpPort,
|
||||
@Value("${dapr.grpc.port}") String daprGrpcPort
|
||||
){
|
||||
return new TestcontainersDaprClientCustomizer(daprHttpEndpoint, daprGrpcEndpoint, daprHttpPort, daprGrpcPort);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DaprMessagingTemplate<String> messagingTemplate(DaprClient daprClient,
|
||||
DaprPubSubProperties daprPubSubProperties) {
|
||||
return new DaprMessagingTemplate<>(daprClient, daprPubSubProperties.getName());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.it.spring.messaging;
|
||||
|
||||
import io.dapr.Topic;
|
||||
import io.dapr.client.domain.CloudEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
public class TestRestController {
|
||||
|
||||
public static final String pubSubName = "pubsub";
|
||||
public static final String topicName = "mockTopic";
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TestRestController.class);
|
||||
private final List<CloudEvent<String>> events = new ArrayList<>();
|
||||
|
||||
@GetMapping("/")
|
||||
public String ok() {
|
||||
return "OK";
|
||||
}
|
||||
|
||||
@Topic(name = topicName, pubsubName = pubSubName)
|
||||
@PostMapping("/subscribe")
|
||||
public void handleMessages(@RequestBody CloudEvent<String> event) {
|
||||
LOG.info("++++++CONSUME {}------", event);
|
||||
events.add(event);
|
||||
}
|
||||
|
||||
public List<CloudEvent<String>> getEvents() {
|
||||
return events;
|
||||
}
|
||||
|
||||
}
|
|
@ -19,11 +19,13 @@ import io.dapr.client.DaprClientBuilder;
|
|||
import io.dapr.client.domain.Metadata;
|
||||
import io.dapr.client.domain.State;
|
||||
|
||||
import io.dapr.config.Properties;
|
||||
import io.dapr.testcontainers.DaprContainer;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
@ -36,19 +38,18 @@ import static com.github.tomakehurst.wiremock.client.WireMock.any;
|
|||
import static com.github.tomakehurst.wiremock.client.WireMock.configureFor;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.get;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.post;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@Testcontainers
|
||||
@WireMockTest(httpPort = 8081)
|
||||
@Tag("testcontainers")
|
||||
public class DaprContainerTest {
|
||||
|
||||
// Time-to-live for messages published.
|
||||
|
@ -59,7 +60,7 @@ public class DaprContainerTest {
|
|||
private static final String PUBSUB_TOPIC_NAME = "topic";
|
||||
|
||||
@Container
|
||||
public static DaprContainer daprContainer = new DaprContainer("daprio/daprd")
|
||||
private static final DaprContainer DAPR_CONTAINER = new DaprContainer("daprio/daprd")
|
||||
.withAppName("dapr-app")
|
||||
.withAppPort(8081)
|
||||
.withAppChannelAddress("host.testcontainers.internal");
|
||||
|
@ -67,15 +68,13 @@ public class DaprContainerTest {
|
|||
/**
|
||||
* Sets the Dapr properties for the test.
|
||||
*/
|
||||
@BeforeAll
|
||||
public static void setDaprProperties() {
|
||||
@BeforeEach
|
||||
public void setDaprProperties() {
|
||||
configStub();
|
||||
org.testcontainers.Testcontainers.exposeHostPorts(8081);
|
||||
System.setProperty("dapr.grpc.port", Integer.toString(daprContainer.getGrpcPort()));
|
||||
System.setProperty("dapr.http.port", Integer.toString(daprContainer.getHttpPort()));
|
||||
}
|
||||
|
||||
private static void configStub() {
|
||||
private void configStub() {
|
||||
stubFor(any(urlMatching("/dapr/subscribe"))
|
||||
.willReturn(aResponse().withBody("[]").withStatus(200)));
|
||||
|
||||
|
@ -94,22 +93,22 @@ public class DaprContainerTest {
|
|||
|
||||
@Test
|
||||
public void testDaprContainerDefaults() {
|
||||
assertEquals(
|
||||
2,
|
||||
daprContainer.getComponents().size(),
|
||||
assertEquals(2,
|
||||
DAPR_CONTAINER.getComponents().size(),
|
||||
"The pubsub and kvstore component should be configured by default"
|
||||
);
|
||||
assertEquals(
|
||||
1,
|
||||
daprContainer.getSubscriptions().size(),
|
||||
"A subscription should be configured by default if none is provided");
|
||||
DAPR_CONTAINER.getSubscriptions().size(),
|
||||
"A subscription should be configured by default if none is provided"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStateStore() throws Exception {
|
||||
try (DaprClient client = (new DaprClientBuilder()).build()) {
|
||||
client.waitForSidecar(1000).block();
|
||||
DaprClientBuilder builder = createDaprClientBuilder();
|
||||
|
||||
try (DaprClient client = (builder).build()) {
|
||||
String value = "value";
|
||||
// Save state
|
||||
client.saveState(STATE_STORE_NAME, KEY, value).block();
|
||||
|
@ -117,24 +116,19 @@ public class DaprContainerTest {
|
|||
// Get the state back from the state store
|
||||
State<String> retrievedState = client.getState(STATE_STORE_NAME, KEY, String.class).block();
|
||||
|
||||
assertEquals("The value retrieved should be the same as the one stored", value, retrievedState.getValue());
|
||||
assertNotNull(retrievedState);
|
||||
assertEquals(value, retrievedState.getValue(), "The value retrieved should be the same as the one stored");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlacement() throws Exception {
|
||||
// Here we are just waiting for Dapr to be ready
|
||||
try (DaprClient client = (new DaprClientBuilder()).build()) {
|
||||
client.waitForSidecar(1000).block();
|
||||
}
|
||||
|
||||
OkHttpClient client = new OkHttpClient.Builder().build();
|
||||
|
||||
String url = "http://" + daprContainer.getHost() + ":" + daprContainer.getMappedPort(3500);
|
||||
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
|
||||
String url = "http://" + DAPR_CONTAINER.getHost() + ":" + DAPR_CONTAINER.getHttpPort();
|
||||
Request request = new Request.Builder().url(url + "/v1.0/metadata").build();
|
||||
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
if (response.isSuccessful()) {
|
||||
try (Response response = okHttpClient.newCall(request).execute()) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
assertTrue(response.body().string().contains("placement: connected"));
|
||||
|
||||
} else {
|
||||
|
@ -145,15 +139,20 @@ public class DaprContainerTest {
|
|||
|
||||
@Test
|
||||
public void testPubSub() throws Exception {
|
||||
try (DaprClient client = (new DaprClientBuilder()).build()) {
|
||||
client.waitForSidecar(1000).block();
|
||||
DaprClientBuilder builder = createDaprClientBuilder();
|
||||
|
||||
try (DaprClient client = (builder).build()) {
|
||||
String message = "message content";
|
||||
Map<String, String> metadata = singletonMap(Metadata.TTL_IN_SECONDS, MESSAGE_TTL_IN_SECONDS);
|
||||
client.publishEvent(PUBSUB_NAME, PUBSUB_TOPIC_NAME, message, metadata).block();
|
||||
}
|
||||
|
||||
verify(getRequestedFor(urlMatching("/dapr/config")));
|
||||
verify(postRequestedFor(urlEqualTo("/events")).withHeader("Content-Type", equalTo("application/cloudevents+json")));
|
||||
}
|
||||
|
||||
private DaprClientBuilder createDaprClientBuilder() {
|
||||
return new DaprClientBuilder()
|
||||
.withPropertyOverride(Properties.HTTP_PORT, String.valueOf(DAPR_CONTAINER.getHttpPort()))
|
||||
.withPropertyOverride(Properties.GRPC_PORT, String.valueOf(DAPR_CONTAINER.getGrpcPort()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ limitations under the License.
|
|||
package io.dapr.it.testcontainers;
|
||||
|
||||
import io.dapr.testcontainers.DaprPlacementContainer;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
@ -21,6 +22,7 @@ import org.testcontainers.junit.jupiter.Testcontainers;
|
|||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@Testcontainers
|
||||
@Tag("testcontainers")
|
||||
public class DaprPlacementContainerTest {
|
||||
|
||||
@Container
|
||||
|
@ -28,6 +30,6 @@ public class DaprPlacementContainerTest {
|
|||
|
||||
@Test
|
||||
public void testDaprPlacementContainerDefaults() {
|
||||
assertEquals(50005, PLACEMENT_CONTAINER.getPort(), "The default port is set");
|
||||
assertEquals(50005, PLACEMENT_CONTAINER.getPort(), "The default port is not set");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package io.dapr.it.testcontainers;
|
||||
|
||||
import io.dapr.testcontainers.Component;
|
||||
import io.dapr.testcontainers.DaprContainer;
|
||||
import io.dapr.testcontainers.DaprLogLevel;
|
||||
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||
import org.springframework.test.context.DynamicPropertySource;
|
||||
import org.testcontainers.Testcontainers;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
public interface DaprTestcontainersModule {
|
||||
|
||||
@Container
|
||||
DaprContainer dapr = new DaprContainer("daprio/daprd:1.13.2")
|
||||
.withAppName("workflow-dapr-app")
|
||||
//Enable Workflows
|
||||
.withComponent(new Component("kvstore", "state.in-memory", "v1",
|
||||
Collections.singletonMap("actorStateStore", "true")))
|
||||
.withComponent(new Component("pubsub", "pubsub.in-memory", "v1", Collections.emptyMap()))
|
||||
.withAppPort(8080)
|
||||
.withDaprLogLevel(DaprLogLevel.DEBUG)
|
||||
.withAppChannelAddress("host.testcontainers.internal");
|
||||
|
||||
/**
|
||||
* Expose the Dapr ports to the host.
|
||||
*
|
||||
* @param registry the dynamic property registry
|
||||
*/
|
||||
@DynamicPropertySource
|
||||
static void daprProperties(DynamicPropertyRegistry registry) {
|
||||
Testcontainers.exposeHostPorts(8080);
|
||||
dapr.start();
|
||||
|
||||
registry.add("dapr.http.endpoint", dapr::getHttpEndpoint);
|
||||
registry.add("dapr.grpc.endpoint", dapr::getGrpcEndpoint);
|
||||
registry.add("dapr.grpc.port", dapr::getGrpcPort);
|
||||
registry.add("dapr.http.port", dapr::getHttpPort);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.it.testcontainers;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.dapr.workflows.client.DaprWorkflowClient;
|
||||
import io.dapr.workflows.client.WorkflowInstanceStatus;
|
||||
import io.dapr.workflows.runtime.WorkflowRuntime;
|
||||
import io.dapr.workflows.runtime.WorkflowRuntimeBuilder;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
@SpringBootTest(
|
||||
webEnvironment = WebEnvironment.RANDOM_PORT,
|
||||
classes = TestWorkflowsApplication.class
|
||||
)
|
||||
@Testcontainers
|
||||
@Tag("testcontainers")
|
||||
public class DaprWorkflowsTests {
|
||||
|
||||
private DaprWorkflowClient workflowClient;
|
||||
|
||||
/**
|
||||
* Initializes the test.
|
||||
*/
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder().registerWorkflow(TestWorkflow.class);
|
||||
builder.registerActivity(FirstActivity.class);
|
||||
builder.registerActivity(SecondActivity.class);
|
||||
|
||||
try (WorkflowRuntime runtime = builder.build()) {
|
||||
System.out.println("Start workflow runtime");
|
||||
runtime.start(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void myWorkflowTest() throws Exception {
|
||||
workflowClient = new DaprWorkflowClient();
|
||||
|
||||
TestWorkflowPayload payload = new TestWorkflowPayload(new ArrayList<>());
|
||||
String instanceId = workflowClient.scheduleNewWorkflow(TestWorkflow.class, payload);
|
||||
|
||||
workflowClient.waitForInstanceStart(instanceId, Duration.ofSeconds(10), false);
|
||||
|
||||
workflowClient.raiseEvent(instanceId, "MoveForward", payload);
|
||||
|
||||
WorkflowInstanceStatus workflowStatus = workflowClient.waitForInstanceCompletion(instanceId,
|
||||
Duration.ofSeconds(10),
|
||||
true);
|
||||
|
||||
// The workflow completed before 10 seconds
|
||||
assertNotNull(workflowStatus);
|
||||
|
||||
String workflowPlayloadJson = workflowStatus.getSerializedOutput();
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
TestWorkflowPayload workflowOutput = mapper.readValue(workflowPlayloadJson, TestWorkflowPayload.class);
|
||||
|
||||
assertEquals(2, workflowOutput.getPayloads().size());
|
||||
assertEquals("First Activity", workflowOutput.getPayloads().get(0));
|
||||
assertEquals("Second Activity", workflowOutput.getPayloads().get(1));
|
||||
assertEquals(instanceId, workflowOutput.getWorkflowId());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.it.testcontainers;
|
||||
|
||||
import io.dapr.workflows.runtime.WorkflowActivity;
|
||||
import io.dapr.workflows.runtime.WorkflowActivityContext;
|
||||
|
||||
public class FirstActivity implements WorkflowActivity {
|
||||
|
||||
@Override
|
||||
public Object run(WorkflowActivityContext ctx) {
|
||||
TestWorkflowPayload workflowPayload = ctx.getInput(TestWorkflowPayload.class);
|
||||
workflowPayload.getPayloads().add("First Activity");
|
||||
return workflowPayload;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.it.testcontainers;
|
||||
|
||||
import io.dapr.workflows.runtime.WorkflowActivity;
|
||||
import io.dapr.workflows.runtime.WorkflowActivityContext;
|
||||
|
||||
public class SecondActivity implements WorkflowActivity {
|
||||
|
||||
@Override
|
||||
public Object run(WorkflowActivityContext ctx) {
|
||||
TestWorkflowPayload workflowPayload = ctx.getInput(TestWorkflowPayload.class);
|
||||
workflowPayload.getPayloads().add("Second Activity");
|
||||
return workflowPayload;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.it.testcontainers;
|
||||
|
||||
import io.dapr.client.domain.CloudEvent;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
public class SubscriptionsRestController {
|
||||
|
||||
private final List<CloudEvent<?>> events = new ArrayList<>();
|
||||
|
||||
@PostMapping(path = "/events", consumes = "application/cloudevents+json")
|
||||
public void receiveEvents(@RequestBody CloudEvent<?> event) {
|
||||
events.add(event);
|
||||
}
|
||||
|
||||
@GetMapping(path = "/events", produces = "application/cloudevents+json")
|
||||
public List<CloudEvent<?>> getAllEvents() {
|
||||
return events;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.it.testcontainers;
|
||||
|
||||
import io.dapr.workflows.Workflow;
|
||||
import io.dapr.workflows.WorkflowStub;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
public class TestWorkflow extends Workflow {
|
||||
|
||||
@Override
|
||||
public WorkflowStub create() {
|
||||
return ctx -> {
|
||||
Logger logger = ctx.getLogger();
|
||||
String instanceId = ctx.getInstanceId();
|
||||
logger.info("Starting Workflow: " + ctx.getName());
|
||||
logger.info("Instance ID: " + instanceId);
|
||||
logger.info("Current Orchestration Time: " + ctx.getCurrentInstant());
|
||||
|
||||
TestWorkflowPayload workflowPayload = ctx.getInput(TestWorkflowPayload.class);
|
||||
workflowPayload.setWorkflowId(instanceId);
|
||||
|
||||
TestWorkflowPayload payloadAfterFirst =
|
||||
ctx.callActivity(FirstActivity.class.getName(), workflowPayload, TestWorkflowPayload.class).await();
|
||||
|
||||
ctx.waitForExternalEvent("MoveForward", Duration.ofSeconds(3), TestWorkflowPayload.class).await();
|
||||
|
||||
TestWorkflowPayload payloadAfterSecond =
|
||||
ctx.callActivity(SecondActivity.class.getName(), payloadAfterFirst, TestWorkflowPayload.class).await();
|
||||
|
||||
ctx.complete(payloadAfterSecond);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.it.testcontainers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TestWorkflowPayload {
|
||||
private List<String> payloads;
|
||||
private String workflowId;
|
||||
|
||||
public TestWorkflowPayload() {
|
||||
}
|
||||
|
||||
public TestWorkflowPayload(List<String> payloads, String workflowId) {
|
||||
this.payloads = payloads;
|
||||
this.workflowId = workflowId;
|
||||
}
|
||||
|
||||
public TestWorkflowPayload(List<String> payloads) {
|
||||
this.payloads = payloads;
|
||||
}
|
||||
|
||||
public List<String> getPayloads() {
|
||||
return payloads;
|
||||
}
|
||||
|
||||
public void setPayloads(List<String> payloads) {
|
||||
this.payloads = payloads;
|
||||
}
|
||||
|
||||
public String getWorkflowId() {
|
||||
return workflowId;
|
||||
}
|
||||
|
||||
public void setWorkflowId(String workflowId) {
|
||||
this.workflowId = workflowId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright 2024 The Dapr 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 io.dapr.it.testcontainers;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.testcontainers.context.ImportTestcontainers;
|
||||
|
||||
@SpringBootApplication
|
||||
public class TestWorkflowsApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(TestWorkflowsApplication.class, args);
|
||||
}
|
||||
|
||||
@ImportTestcontainers(DaprTestcontainersModule.class)
|
||||
static class DaprTestConfiguration {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"filter": {
|
||||
"EQ": {
|
||||
"content": "test"
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"limit": 0,
|
||||
"token": "0"
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<project
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
|
30
sdk/pom.xml
30
sdk/pom.xml
|
@ -1,7 +1,7 @@
|
|||
<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">
|
||||
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>
|
||||
|
@ -143,20 +143,20 @@
|
|||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
<includes>
|
||||
<include>**/sdk_version.properties</include>
|
||||
</includes>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
<includes>
|
||||
<include>**/sdk_version.properties</include>
|
||||
</includes>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>false</filtering>
|
||||
<excludes>
|
||||
<exclude>**/sdk_version.properties</exclude>
|
||||
</excludes>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>false</filtering>
|
||||
<excludes>
|
||||
<exclude>**/sdk_version.properties</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
</resources>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
|
@ -215,7 +215,7 @@
|
|||
<rule>
|
||||
<element>BUNDLE</element>
|
||||
<limits>
|
||||
<limit>
|
||||
<limit>
|
||||
<counter>LINE</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>80%</minimum>
|
||||
|
|
|
@ -34,7 +34,13 @@
|
|||
<groupId>io.dapr</groupId>
|
||||
<artifactId>dapr-sdk</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>test</scope>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dapr.spring</groupId>
|
||||
<artifactId>dapr-spring-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
|
|
@ -161,6 +161,10 @@ public class DaprContainer extends GenericContainer<DaprContainer> {
|
|||
return getMappedPort(DAPRD_DEFAULT_GRPC_PORT);
|
||||
}
|
||||
|
||||
public String getGrpcEndpoint() {
|
||||
return ":" + getMappedPort(DAPRD_DEFAULT_GRPC_PORT);
|
||||
}
|
||||
|
||||
public DaprContainer withAppChannelAddress(String appChannelAddress) {
|
||||
this.appChannelAddress = appChannelAddress;
|
||||
return this;
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package io.dapr.testcontainers;
|
||||
|
||||
import io.dapr.client.DaprClientBuilder;
|
||||
import io.dapr.config.Properties;
|
||||
import io.dapr.spring.core.client.DaprClientCustomizer;
|
||||
|
||||
public class TestcontainersDaprClientCustomizer implements DaprClientCustomizer {
|
||||
|
||||
private final String httpEndpoint;
|
||||
private final String grpcEndpoint;
|
||||
private final String daprHttpPort;
|
||||
private final String daprGrpcPort;
|
||||
|
||||
/**
|
||||
* Constructor for TestcontainersDaprClientCustomizer.
|
||||
* @param httpEndpoint HTTP endpoint.
|
||||
* @param grpcEndpoint GRPC endpoint.
|
||||
* @param daprHttpPort Dapr HTTP port.
|
||||
* @param daprGrpcPort Dapr GRPC port.
|
||||
*/
|
||||
public TestcontainersDaprClientCustomizer(
|
||||
String httpEndpoint,
|
||||
String grpcEndpoint,
|
||||
String daprHttpPort,
|
||||
String daprGrpcPort
|
||||
) {
|
||||
this.httpEndpoint = httpEndpoint;
|
||||
this.grpcEndpoint = grpcEndpoint;
|
||||
this.daprHttpPort = daprHttpPort;
|
||||
this.daprGrpcPort = daprGrpcPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(DaprClientBuilder daprClientBuilder) {
|
||||
daprClientBuilder.withPropertyOverride(Properties.HTTP_ENDPOINT, httpEndpoint);
|
||||
daprClientBuilder.withPropertyOverride(Properties.GRPC_ENDPOINT, grpcEndpoint);
|
||||
daprClientBuilder.withPropertyOverride(Properties.HTTP_PORT, daprHttpPort);
|
||||
daprClientBuilder.withPropertyOverride(Properties.GRPC_PORT, daprGrpcPort);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue