Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f5cd34f
dbeaver/pro#11108 Add external DAM transport contracts
vkirst Oct 19, 2026
1a15585
dbeaver/pro#11108 Add service-authenticated External DAM GraphQL client
vkirst Oct 19, 2026
2b04e9b
dbeaver/pro#11108 Fix External DAM API Checkstyle warnings
vkirst Oct 20, 2026
c5ec67d
dbeaver/pro#11108 Fix External DAM protocol separation and OSGi visib…
vkirst Oct 20, 2026
34b34ea
dbeaver/pro#11108 Add backend subscription clients and channel status…
vkirst Oct 21, 2026
e39ab29
dbeaver/pro#11108 Add transport-independent request/reply messaging
vkirst Oct 21, 2026
21477d0
dbeaver/pro#11108 Add missing interface Javadoc
vkirst Oct 21, 2026
6edb503
dbeaver/pro#11108 Add transport-independent typed RPC
vkirst Oct 21, 2026
7343246
dbeaver/pro#11108 Add internal connection account lookup
vkirst Oct 21, 2026
3b8802b
dbeaver/pro#11108 Add per-binding codecs and direct RPC topics
vkirst Oct 22, 2026
e289d46
dbeaver/pro#11108 Add dynamic subscription callback metadata
vkirst Oct 22, 2026
601b5dc
dbeaver/pro#11108 Preserve typed transport send failures
vkirst Oct 22, 2026
f2bc61b
Merge branch 'devel' into dbeaver/pro#11108-desktop-websocket-transport
vkirst Oct 22, 2026
5dad707
dbeaver/pro#11108 Allow per-proxy RPC timeouts
vkirst Oct 23, 2026
8252e2b
dbeaver/pro#11108 Centralize External DAM protocol and subscription c…
vkirst Oct 23, 2026
2e9d4d2
dbeaver/pro#11108 Allow shared service secret for subscription callbacks
vkirst Oct 23, 2026
e028dfb
dbeaver/pro#11108 Merge messaging into renamed External DAM API module
vkirst Oct 23, 2026
aaf1ad7
dbeaver/pro#11108 Remove RPC topic subscription quota
vkirst Oct 23, 2026
79dc9c8
dbeaver/pro#11108 Decouple RPC execution and use nonblocking HTTP
vkirst Oct 24, 2026
cb1a626
dbeaver/pro#11108 Preserve remote RPC exception diagnostics
vkirst Oct 25, 2026
4974a44
dbeaver/pro#11108 Fix async External DAM cancellation and lint
vkirst Oct 25, 2026
f71b0da
dbeaver/pro#11108 Avoid Gson wording assumptions in RPC tests
vkirst Oct 25, 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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ This repository contains API for global DBeaver ecosystem.
OSGI APIs and Service for DataDam integration
Used by DBeaver, dbvr, CloudBeaver and by DataDam itself

## External DAM API

[`apis/com.dbeaver.datadam.external-dam.api`](apis/com.dbeaver.datadam.external-dam.api/README.md) contains the External DAM
contracts, clients and transport adapters, together with the messaging implementation. Java packages remain
`com.dbeaver.datadam.external.api.*` and `com.dbeaver.messaging.*`; messaging is not a separate Maven or OSGi module.

## SSO API

`apis/com.dbeaver.datadam.sso.api` exports the shared SSO contracts in
Expand Down
271 changes: 271 additions & 0 deletions apis/com.dbeaver.datadam.external-dam.api/MESSAGING.md

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions apis/com.dbeaver.datadam.external-dam.api/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: DataDam External DAM API
Bundle-SymbolicName: com.dbeaver.datadam.external-dam.api

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets not use dashes in module names.
Maybe just com.dbeaver.datadam.dam.api ?

Bundle-Version: 0.0.17.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-21
Export-Package: com.dbeaver.datadam.external.api,
com.dbeaver.datadam.external.api.client,
com.dbeaver.datadam.external.api.exception,
com.dbeaver.datadam.external.api.model,
com.dbeaver.datadam.external.api.requestreply,
com.dbeaver.datadam.external.api.rpc,
com.dbeaver.datadam.external.api.transport,
com.dbeaver.messaging.requestreply,
com.dbeaver.messaging.rpc
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.utils,
com.dbeaver.rest.client;visibility:=reexport
Import-Package: com.google.gson
Automatic-Module-Name: com.dbeaver.datadam.external.dam.api
396 changes: 396 additions & 0 deletions apis/com.dbeaver.datadam.external-dam.api/README.md

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions apis/com.dbeaver.datadam.external-dam.api/build.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
source.. = src/main/java
output.. = target/classes/
bin.includes = .,\
META-INF/
63 changes: 63 additions & 0 deletions apis/com.dbeaver.datadam.external-dam.api/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?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>com.dbeaver.datadam</groupId>
<artifactId>api</artifactId>
<version>0.0.17-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

<artifactId>com.dbeaver.datadam.external-dam.api</artifactId>
<packaging>eclipse-plugin</packaging>
<name>DataDam External DAM API</name>

<dependencies>
<dependency>
<groupId>com.dbeaver.common</groupId>
<artifactId>org.jkiss.utils</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>com.dbeaver.common</groupId>
<artifactId>com.dbeaver.rest.client</artifactId>
<version>${dbeaver.common.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory>

<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<executions>
<execution>
<id>test</id>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 DBeaver Corp
*
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of DBeaver Corp and its suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to DBeaver Corp and its suppliers
* and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from DBeaver Corp.
*/
package com.dbeaver.datadam.external.api;

import org.jkiss.code.Nullable;

import java.net.URI;

public final class DDExternalDamApi {
public static final String GRAPHQL_PATH = "/internal/graphql";
public static final String CALLBACK_GRAPHQL_PATH = "/internal/external-dam/graphql";
public static final String HEADER_SERVICE_TOKEN = "X-DD-Service-Token";
public static final int MIN_SERVICE_SECRET_LENGTH = 32;
public static final int MAX_SERVICE_SECRET_LENGTH = 512;
public static final int MIN_SUBSCRIPTION_LEASE_SECONDS = 15;
public static final int MAX_SUBSCRIPTION_LEASE_SECONDS = 300;
private static final int MAX_SUBSCRIBER_ID_LENGTH = 256;
private static final int MAX_SERVICE_URL_LENGTH = 4096;

private DDExternalDamApi() {
}

public static boolean isValidSubscriberId(@Nullable String subscriberId) {
return subscriberId != null && !subscriberId.isBlank() && subscriberId.length() <= MAX_SUBSCRIBER_ID_LENGTH;
}

public static boolean isValidServiceSecret(@Nullable String secret) {
return secret != null && secret.length() >= MIN_SERVICE_SECRET_LENGTH && secret.length() <= MAX_SERVICE_SECRET_LENGTH
&& secret.chars().allMatch(value -> value >= 33 && value <= 126);
}

public static boolean isValidServiceUrl(@Nullable String serviceUrl) {
if (serviceUrl == null || serviceUrl.length() > MAX_SERVICE_URL_LENGTH) {
return false;
}
try {
URI uri = URI.create(serviceUrl);
return uri.getHost() != null && uri.getUserInfo() == null && uri.getRawQuery() == null && uri.getRawFragment() == null
&& ("https".equalsIgnoreCase(uri.getScheme()) || "http".equalsIgnoreCase(uri.getScheme()))
&& (uri.getRawPath().isEmpty() || "/".equals(uri.getRawPath()))
&& (uri.getPort() == -1 || uri.getPort() >= 1 && uri.getPort() <= 65535);
} catch (IllegalArgumentException e) {
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 DBeaver Corp
*
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of DBeaver Corp and its suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to DBeaver Corp and its suppliers
* and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from DBeaver Corp.
*/
package com.dbeaver.datadam.external.api;

import com.dbeaver.datadam.external.api.model.DDHello;
import com.dbeaver.datadam.external.api.model.DDMessage;
import com.dbeaver.datadam.external.api.model.DDReady;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import org.jkiss.code.NotNull;

public final class DDExternalDamProtocol {
public static final String WEBSOCKET_PATH = "/ws";
public static final String TYPE_HELLO = "hello";
public static final String TYPE_READY = "ready";

private static final String FIELD_TOPIC = "topic";
private static final String FIELD_TYPE = "type";
private static final String FIELD_PAYLOAD = "payload";
private static final String FIELD_DEPLOYMENT_ID = "deploymentId";
private static final String FIELD_APPLICATION_RUN_ID = "applicationRunId";
private static final String FIELD_PRODUCT = "product";
private static final String FIELD_VERSION = "version";
private static final String FIELD_CONNECTION_ID = "connectionId";

private DDExternalDamProtocol() {
}

@NotNull
public static String encode(@NotNull DDMessage message) {
JsonObject envelope = envelope(message.type(), message.payload());
envelope.addProperty(FIELD_TOPIC, message.topic());
return envelope.toString();
}

@NotNull
public static DDMessage decode(@NotNull String message) {
JsonObject envelope = parseEnvelope(message);
return new DDMessage(
readString(envelope, FIELD_TOPIC),
readString(envelope, FIELD_TYPE),
readPayload(envelope)
);
}

@NotNull
public static String encodeHello(@NotNull DDHello hello) {
JsonObject payload = new JsonObject();
payload.addProperty(FIELD_DEPLOYMENT_ID, hello.deploymentId());
payload.addProperty(FIELD_APPLICATION_RUN_ID, hello.applicationRunId());
payload.addProperty(FIELD_PRODUCT, hello.product());
payload.addProperty(FIELD_VERSION, hello.version());
return envelope(TYPE_HELLO, payload).toString();
}

@NotNull
public static DDHello decodeHello(@NotNull String message) {
JsonObject payload = readControlPayload(message, TYPE_HELLO);
return new DDHello(
readString(payload, FIELD_DEPLOYMENT_ID),
readString(payload, FIELD_APPLICATION_RUN_ID),
readString(payload, FIELD_PRODUCT),
readString(payload, FIELD_VERSION)
);
}

@NotNull
public static String encodeReady(@NotNull DDReady ready) {
JsonObject payload = new JsonObject();
payload.addProperty(FIELD_CONNECTION_ID, ready.connectionId());
return envelope(TYPE_READY, payload).toString();
}

@NotNull
public static DDReady decodeReady(@NotNull String message) {
return new DDReady(readString(readControlPayload(message, TYPE_READY), FIELD_CONNECTION_ID));
}

@NotNull
private static JsonObject envelope(@NotNull String type, @NotNull JsonObject payload) {
JsonObject envelope = new JsonObject();
envelope.addProperty(FIELD_TYPE, type);
envelope.add(FIELD_PAYLOAD, payload);
return envelope;
}

@NotNull
private static JsonObject parseEnvelope(@NotNull String message) {
try {
JsonElement parsed = JsonParser.parseString(message);
if (!parsed.isJsonObject()) {
throw new IllegalArgumentException("Expected a message object");
}
return parsed.getAsJsonObject();
} catch (JsonParseException e) {
throw new IllegalArgumentException("Invalid message JSON", e);
}
}

@NotNull
private static JsonObject readControlPayload(@NotNull String message, @NotNull String expectedType) {
JsonObject envelope = parseEnvelope(message);
if (envelope.has(FIELD_TOPIC) || !expectedType.equals(readString(envelope, FIELD_TYPE))) {
throw new IllegalArgumentException("Expected a " + expectedType + " message");
}
return readPayload(envelope);
}

@NotNull
private static JsonObject readPayload(@NotNull JsonObject envelope) {
JsonElement payload = envelope.get(FIELD_PAYLOAD);
if (payload == null || !payload.isJsonObject()) {
throw new IllegalArgumentException("Expected a payload object");
}
return payload.getAsJsonObject();
}

@NotNull
private static String readString(@NotNull JsonObject object, @NotNull String field) {
JsonElement value = object.get(field);
if (value == null || !value.isJsonPrimitive() || !value.getAsJsonPrimitive().isString()
|| value.getAsString().isBlank()) {
throw new IllegalArgumentException("Expected a non-blank string for " + field);
}
return value.getAsString();
}
}
Loading
Loading