Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e3108d3
adding spring boot 4.0.2 tests to the matrix
salaboy Feb 2, 2026
961f2a6
removing ignoring autoconfig for hibernate and datasource
salaboy Feb 2, 2026
b807201
adding spring-data-6 and spring-boot-4-starters
salaboy Feb 9, 2026
519e0c7
adding spring-boot-4-sdk-tests to split dependencies
salaboy Feb 9, 2026
c22d548
new profile for sb4
salaboy Feb 10, 2026
df81125
Update master version to 1.18.0-SNAPSHOT
dapr-bot Feb 2, 2026
3a673aa
feat: Add workflow versioning (#1624)
javier-aliaga Feb 6, 2026
1a8f977
feat: Add new fields to conversation api (#1640)
javier-aliaga Feb 6, 2026
8759488
feat: Add failure policy to actor reminders (#1643)
javier-aliaga Feb 10, 2026
cfc7779
updating version
salaboy Feb 10, 2026
d8f1e79
modifying integration tests steps for github workflow
salaboy Feb 10, 2026
dfceb37
fixing workflow
salaboy Feb 10, 2026
ae222d6
modified version on matrix
salaboy Feb 10, 2026
6cbf37f
modified values for matrix
salaboy Feb 10, 2026
9cc8cd8
fix pom reference
salaboy Feb 10, 2026
70c150d
moving spring boot examples to integration tests
salaboy Feb 11, 2026
4885765
removing code dependency between examples
salaboy Feb 11, 2026
807d4d8
updating sb4 examples and deps
salaboy Feb 11, 2026
a058b18
Add PATCH constant to HttpExtension for service invocation (#1644)
lindner Feb 12, 2026
5282ca5
exclude spring6-data from java docs as it is not compatible with Spri…
salaboy Feb 12, 2026
36a0aad
feat: Use dapr 1.17.0-rc.6 (#1651)
javier-aliaga Feb 12, 2026
d4ccbc4
fixing classpath for javadocs aggregation
salaboy Feb 13, 2026
58daddf
Merge branch 'master' into spring-boot-4
salaboy Feb 13, 2026
9e00635
Merge branch 'master' into spring-boot-4
salaboy Feb 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ jobs:
experimental: [ false ]
include:
- java: 17
spring-boot-version: 3.4.9
spring-boot-display-version: 3.4.x
spring-boot-version: 4.0.2
spring-boot-display-version: 4.0.x
experimental: false
env:
GOVER: "1.20"
Expand Down Expand Up @@ -197,21 +197,38 @@ jobs:
/home/runner/.local/bin/toxiproxy-server --version
- name: Clean up and install sdk
run: ./mvnw clean install -B -q -DskipTests
- name: Integration tests using spring boot version ${{ matrix.spring-boot-version }}
- name: Integration tests using spring boot 3.x version ${{ matrix.spring-boot-version }}
id: integration_tests
if: ${{ matrix.spring-boot-display-version == '3.5.x' }}
run: PRODUCT_SPRING_BOOT_VERSION=${{ matrix.spring-boot-version }} ./mvnw -B -pl !durabletask-client -Pintegration-tests dependency:copy-dependencies verify
- name: Integration tests using spring boot 4.x version ${{ matrix.spring-boot-version }}
id: integration_sb4_tests
if: ${{ matrix.spring-boot-display-version == '4.0.x' }}
run: PRODUCT_SPRING_BOOT_VERSION=${{ matrix.spring-boot-version }} ./mvnw -B -pl !durabletask-client -Pintegration-sb4-tests dependency:copy-dependencies verify
- name: Upload failsafe test report for sdk-tests on failure
if: ${{ failure() && steps.integration_tests.conclusion == 'failure' }}
uses: actions/upload-artifact@v6
with:
name: failsafe-report-sdk-tests-jdk${{ matrix.java }}-sb${{ matrix.spring-boot-version }}
path: sdk-tests/target/failsafe-reports
- name: Upload failsafe test report for sb4 sdk-tests on failure
if: ${{ failure() && steps.integration_sb4_tests.conclusion == 'failure' }}
uses: actions/upload-artifact@v6
with:
name: failsafe-report-sdk-tests-jdk${{ matrix.java }}-sb${{ matrix.spring-boot-version }}
path: spring-boot-4-sdk-tests/target/failsafe-reports
- name: Upload surefire test report for sdk-tests on failure
if: ${{ failure() && steps.integration_tests.conclusion == 'failure' }}
uses: actions/upload-artifact@v6
with:
name: surefire-report-sdk-tests-jdk${{ matrix.java }}-sb${{ matrix.spring-boot-version }}
path: sdk-tests/target/surefire-reports
- name: Upload surefire test report for sdk-tests on failure
if: ${{ failure() && steps.integration_sb4_tests.conclusion == 'failure' }}
uses: actions/upload-artifact@v6
with:
name: surefire-report-sdk-tests-jdk${{ matrix.java }}-sb${{ matrix.spring-boot-version }}
path: spring-boot-4-sdk-tests/target/surefire-reports


publish:
Expand Down
58 changes: 58 additions & 0 deletions dapr-spring/dapr-spring-6-data/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.dapr.spring</groupId>
<artifactId>dapr-spring-parent</artifactId>
<version>1.18.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

<artifactId>dapr-spring-6-data</artifactId>

<properties>
<springboot4.version>4.0.2</springboot4.version>
<!-- Override JUnit version to align with Spring Boot 4.0.2 -->
<junit-bom.version>6.0.2</junit-bom.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${springboot4.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-keyvalue</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency>
<dependency>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* 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.spring6.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.Map;

public abstract class AbstractDaprKeyValueAdapter implements KeyValueAdapter {
private static final Map<String, String> CONTENT_TYPE_META = Map.of(
"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<Object> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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.spring6.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();
}
}
Loading