Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -638,5 +638,6 @@ To contribute an integration, please see [CONTRIBUTING_INTEGRATIONS.md](CONTRIBU
* [reference/grpc/README.md](reference/grpc/README.md) - gRPC Reference implementation, based on Quarkus.
* https://github.com/wildfly-extras/a2a-java-sdk-server-jakarta - This integration is based on Jakarta EE, and should work in all runtimes supporting the [Jakarta EE Web Profile](https://jakarta.ee/specifications/webprofile/).


# Extras
See the [`extras`](./extras/README.md) folder for extra functionality not provided by the SDK itself!

7 changes: 7 additions & 0 deletions extras/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# A2A Java SDK - Extras

This directory contains additions to what is provided by the default SDK implementations.

Please see the README's of each child directory for more details.

[`taskstore-database-jpa`](./taskstore-database-jpa/README.md) - Replaces the default `InMemoryTaskStore` with a `TaskStore` backed by a RDBMS. It uses JPA to interact with the RDBMS.
79 changes: 79 additions & 0 deletions extras/taskstore-database-jpa/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# A2A Java SDK - JPA Database TaskStore

This module provides a JPA-based implementation of the `TaskStore` interface that persists tasks to a relational database instead of keeping them in memory.

The persistence is done with the Jakarta Persistence API, so this should be suitable for any JPA 3.0+ provider and Jakarta EE application server.

## Quick Start

### 1. Add Dependency

Add this module to your project's `pom.xml`:

```xml
<dependency>
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-extras-taskstore-database-jpa</artifactId>
<version>${a2a.version}</version>
</dependency>
```

The `JpaDatabaseTaskStore` is annotated in such a way that it should take precedence over the default `InMemoryTaskStore`. Hence, it is a drop-in replacement.

### 2. Configure Database

The following examples assume you are using PostgreSQL as your database. To use another database, adjust as needed for your environment.

#### For Quarkus Reference Servers

Add to your `application.properties`:

```properties
# Database configuration
quarkus.datasource.db-kind=postgresql
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/a2a_tasks
quarkus.datasource.username=your_username
quarkus.datasource.password=your_password

# Hibernate configuration
quarkus.hibernate-orm.database.generation=update
```

#### For WildFly/Jakarta EE Servers

Create or update your `persistence.xml`:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence" version="3.0">
<persistence-unit name="a2a-java" transaction-type="JTA">
<jta-data-source>java:jboss/datasources/A2ATasksDS</jta-data-source>

<class>io.a2a.extras.taskstore.database.jpa.JpaTask</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>

<properties>
<!-- Change as required for your environment -->
<property name="jakarta.persistence.schema-generation.database.action" value="create"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
</properties>
</persistence-unit>
</persistence>
```

### 3. Database Schema

The module will automatically create the required table:

```sql
CREATE TABLE a2a_tasks (
task_id VARCHAR(255) PRIMARY KEY,
task_data TEXT NOT NULL
);
```

## Configuration Options

### Persistence Unit Name

The module uses the persistence unit name `"a2a-java"`. Ensure your `persistence.xml` defines a persistence unit with this name.
102 changes: 102 additions & 0 deletions extras/taskstore-database-jpa/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-sdk-parent</artifactId>
<version>0.3.0.Beta2-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>a2a-java-extras-taskstore-database-jpa</artifactId>

<packaging>jar</packaging>

<name>Java A2A Extras: JPA Database TaskStore</name>
<description>Java SDK for the Agent2Agent Protocol (A2A) - Extras - JPA Database TaskStore</description>

<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>a2a-java-sdk-server-common</artifactId>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-jackson</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.transaction</groupId>
<artifactId>jakarta.transaction-api</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-h2</artifactId>
<scope>test</scope>
</dependency>
<!-- Additional dependencies for integration tests (from reference/jsonrpc) -->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>a2a-java-sdk-reference-jsonrpc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>a2a-java-sdk-client-transport-jsonrpc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>a2a-java-sdk-client</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-reactive-routes</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package io.a2a.extras.taskstore.database.jpa;

import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Alternative;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;

import com.fasterxml.jackson.core.JsonProcessingException;
import io.a2a.server.tasks.TaskStore;
import io.a2a.spec.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@ApplicationScoped
@Alternative
@Priority(50)
public class JpaDatabaseTaskStore implements TaskStore {

private static final Logger LOGGER = LoggerFactory.getLogger(JpaDatabaseTaskStore.class);

@PersistenceContext(unitName = "a2a-java")
EntityManager em;

@Transactional
@Override
public void save(Task task) {
LOGGER.debug("Saving task with ID: {}", task.getId());
try {
JpaTask jpaTask = JpaTask.createFromTask(task);
em.merge(jpaTask);
LOGGER.debug("Persisted/updated task with ID: {}", task.getId());
} catch (JsonProcessingException e) {
LOGGER.error("Failed to serialize task with ID: {}", task.getId(), e);
throw new RuntimeException("Failed to serialize task with ID: " + task.getId(), e);
}
}

@Transactional
@Override
public Task get(String taskId) {
LOGGER.debug("Retrieving task with ID: {}", taskId);
JpaTask jpaTask = em.find(JpaTask.class, taskId);
if (jpaTask == null) {
LOGGER.debug("Task not found with ID: {}", taskId);
return null;
}

try {
Task task = jpaTask.getTask();
LOGGER.debug("Successfully retrieved task with ID: {}", taskId);
return task;
} catch (JsonProcessingException e) {
LOGGER.error("Failed to deserialize task with ID: {}", taskId, e);
throw new RuntimeException("Failed to deserialize task with ID: " + taskId, e);
}
}

@Transactional
@Override
public void delete(String taskId) {
LOGGER.debug("Deleting task with ID: {}", taskId);
JpaTask jpaTask = em.find(JpaTask.class, taskId);
if (jpaTask != null) {
em.remove(jpaTask);
LOGGER.debug("Successfully deleted task with ID: {}", taskId);
} else {
LOGGER.debug("Task not found for deletion with ID: {}", taskId);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package io.a2a.extras.taskstore.database.jpa;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;

import com.fasterxml.jackson.core.JsonProcessingException;
import io.a2a.spec.Task;
import io.a2a.util.Utils;

@Entity
@Table(name = "a2a_tasks")
public class JpaTask {
@Id
@Column(name = "task_id")
private String id;

@Column(name = "task_data", columnDefinition = "TEXT", nullable = false)
private String taskJson;

@Transient
private Task task;

// Default constructor required by JPA
public JpaTask() {
}

public JpaTask(String id, String taskJson) {
this.id = id;
this.taskJson = taskJson;
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getTaskJson() {
return taskJson;
}

public void setTaskJson(String taskJson) {
this.taskJson = taskJson;
}

public Task getTask() throws JsonProcessingException {
if (task == null) {
this.task = Utils.unmarshalFrom(taskJson, Task.TYPE_REFERENCE);
}
return task;
}

public void setTask(Task task) throws JsonProcessingException {
taskJson = Utils.OBJECT_MAPPER.writeValueAsString(task);
if (id == null) {
id = task.getId();
}
this.task = task;
}

static JpaTask createFromTask(Task task) throws JsonProcessingException {
String json = Utils.OBJECT_MAPPER.writeValueAsString(task);
JpaTask jpaTask = new JpaTask(task.getId(), json);
jpaTask.task = task;
return jpaTask;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd">
</beans>
Loading