Skip to content

Commit 656cc15

Browse files
PerfectSlayerclaude
andcommitted
feat(ffe): Add dd-openfeature SDK settings and configuration delivery
Add the SDK settings, the connector SPI the agent can implement, the agentless configuration delivery from the CDN and the direct intake event transport. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent a81ed9e commit 656cc15

32 files changed

Lines changed: 2654 additions & 36 deletions

‎dd-openfeature/build.gradle.kts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ plugins {
55
`java-library`
66
idea
77
id("dd-trace-java.conventions.java")
8+
id("dd-trace-java.version-file")
89
}
910

1011
description = "Datadog OpenFeature provider SDK."

‎dd-openfeature/gradle.lockfile‎

Lines changed: 43 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,77 +2,84 @@
22
# Manual edits can break the build and are not advised.
33
# This file is expected to be part of source control.
44
# To regenerate this file, run: ./gradlew :dd-openfeature:dependencies --write-locks
5-
ch.qos.logback:logback-classic:1.2.13=testCompileClasspath,testRuntimeClasspath
6-
ch.qos.logback:logback-core:1.2.13=testCompileClasspath,testRuntimeClasspath
7-
com.fasterxml.jackson.core:jackson-core:2.20.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
8-
com.fasterxml.jackson:jackson-bom:2.20.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
5+
ch.qos.logback:logback-classic:1.2.13=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
6+
ch.qos.logback:logback-core:1.2.13=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
7+
com.fasterxml.jackson.core:jackson-core:2.20.0=compileClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
8+
com.fasterxml.jackson:jackson-bom:2.20.0=compileClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
99
com.github.javaparser:javaparser-core:3.25.6=codenarc
10-
com.github.spotbugs:spotbugs-annotations:4.10.4=compileClasspath,spotbugs
10+
com.github.spotbugs:spotbugs-annotations:4.10.4=compileClasspath,jmhCompileClasspath,spotbugs
1111
com.github.spotbugs:spotbugs:4.10.4=spotbugs
1212
com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs
13-
com.google.code.findbugs:jsr305:3.0.2=compileClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath
13+
com.google.code.findbugs:jsr305:3.0.2=compileClasspath,jmhCompileClasspath,jmhRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath
1414
com.google.code.gson:gson:2.14.0=spotbugs
1515
com.google.errorprone:error_prone_annotations:2.48.0=spotbugs
1616
com.thoughtworks.qdox:qdox:1.12.1=codenarc
1717
commons-io:commons-io:2.21.0=spotbugs
18-
de.thetaphi:forbiddenapis:3.11=compileClasspath
19-
dev.openfeature:sdk:1.22.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
20-
io.leangen.geantyref:geantyref:1.3.16=testRuntimeClasspath
18+
de.thetaphi:forbiddenapis:3.11=compileClasspath,jmhCompileClasspath
19+
dev.openfeature:sdk:1.22.0=compileClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
20+
io.leangen.geantyref:geantyref:1.3.16=jmhRuntimeClasspath,testRuntimeClasspath
2121
jaxen:jaxen:2.0.6=spotbugs
22-
net.bytebuddy:byte-buddy-agent:1.18.12=testCompileClasspath,testRuntimeClasspath
23-
net.bytebuddy:byte-buddy:1.18.12=testCompileClasspath,testRuntimeClasspath
22+
net.bytebuddy:byte-buddy-agent:1.18.12=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
23+
net.bytebuddy:byte-buddy:1.18.12=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
24+
net.sf.jopt-simple:jopt-simple:5.0.4=jmh,jmhCompileClasspath,jmhRuntimeClasspath
2425
net.sf.saxon:Saxon-HE:12.10=spotbugs
2526
org.apache.ant:ant-antlr:1.10.14=codenarc
2627
org.apache.ant:ant-junit:1.10.14=codenarc
2728
org.apache.bcel:bcel:6.12.0=spotbugs
2829
org.apache.commons:commons-lang3:3.20.0=spotbugs
30+
org.apache.commons:commons-math3:3.6.1=jmh,jmhCompileClasspath,jmhRuntimeClasspath
2931
org.apache.commons:commons-text:1.15.0=spotbugs
3032
org.apache.logging.log4j:log4j-api:2.26.1=spotbugs
3133
org.apache.logging.log4j:log4j-core:2.26.1=spotbugs
32-
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testRuntimeClasspath
34+
org.apiguardian:apiguardian-api:1.1.2=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
3335
org.codehaus.groovy:groovy-ant:3.0.23=codenarc
3436
org.codehaus.groovy:groovy-docgenerator:3.0.23=codenarc
3537
org.codehaus.groovy:groovy-groovydoc:3.0.23=codenarc
3638
org.codehaus.groovy:groovy-json:3.0.23=codenarc
37-
org.codehaus.groovy:groovy-json:3.0.25=testCompileClasspath,testRuntimeClasspath
39+
org.codehaus.groovy:groovy-json:3.0.25=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
3840
org.codehaus.groovy:groovy-templates:3.0.23=codenarc
3941
org.codehaus.groovy:groovy-xml:3.0.23=codenarc
4042
org.codehaus.groovy:groovy:3.0.23=codenarc
41-
org.codehaus.groovy:groovy:3.0.25=testCompileClasspath,testRuntimeClasspath
43+
org.codehaus.groovy:groovy:3.0.25=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
4244
org.codenarc:CodeNarc:3.7.0=codenarc
4345
org.dom4j:dom4j:2.2.0=spotbugs
4446
org.gmetrics:GMetrics:2.1.0=codenarc
45-
org.hamcrest:hamcrest:3.0=testCompileClasspath,testRuntimeClasspath
47+
org.hamcrest:hamcrest:3.0=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
4648
org.jacoco:org.jacoco.agent:0.8.15=jacocoAgent,jacocoAnt
4749
org.jacoco:org.jacoco.ant:0.8.15=jacocoAnt
4850
org.jacoco:org.jacoco.core:0.8.15=jacocoAnt
4951
org.jacoco:org.jacoco.report:0.8.15=jacocoAnt
50-
org.junit.jupiter:junit-jupiter-api:5.14.1=testCompileClasspath,testRuntimeClasspath
51-
org.junit.jupiter:junit-jupiter-engine:5.14.1=testRuntimeClasspath
52-
org.junit.jupiter:junit-jupiter-params:5.14.1=testCompileClasspath,testRuntimeClasspath
53-
org.junit.jupiter:junit-jupiter:5.14.1=testCompileClasspath,testRuntimeClasspath
54-
org.junit.platform:junit-platform-commons:1.14.1=testCompileClasspath,testRuntimeClasspath
55-
org.junit.platform:junit-platform-engine:1.14.1=testCompileClasspath,testRuntimeClasspath
56-
org.junit.platform:junit-platform-launcher:1.14.1=testRuntimeClasspath
57-
org.junit:junit-bom:5.14.1=testCompileClasspath,testRuntimeClasspath
58-
org.mockito:mockito-core:4.4.0=testCompileClasspath,testRuntimeClasspath
59-
org.mockito:mockito-junit-jupiter:4.4.0=testCompileClasspath,testRuntimeClasspath
60-
org.objenesis:objenesis:3.3=testCompileClasspath,testRuntimeClasspath
61-
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath
52+
org.junit.jupiter:junit-jupiter-api:5.14.1=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
53+
org.junit.jupiter:junit-jupiter-engine:5.14.1=jmhRuntimeClasspath,testRuntimeClasspath
54+
org.junit.jupiter:junit-jupiter-params:5.14.1=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
55+
org.junit.jupiter:junit-jupiter:5.14.1=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
56+
org.junit.platform:junit-platform-commons:1.14.1=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
57+
org.junit.platform:junit-platform-engine:1.14.1=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
58+
org.junit.platform:junit-platform-launcher:1.14.1=jmhRuntimeClasspath,testRuntimeClasspath
59+
org.junit:junit-bom:5.14.1=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
60+
org.mockito:mockito-core:4.4.0=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
61+
org.mockito:mockito-junit-jupiter:4.4.0=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
62+
org.objenesis:objenesis:3.3=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
63+
org.openjdk.jmh:jmh-core:1.37=jmh,jmhCompileClasspath,jmhRuntimeClasspath
64+
org.openjdk.jmh:jmh-generator-asm:1.37=jmh,jmhCompileClasspath,jmhRuntimeClasspath
65+
org.openjdk.jmh:jmh-generator-bytecode:1.37=jmh,jmhCompileClasspath,jmhRuntimeClasspath
66+
org.openjdk.jmh:jmh-generator-reflection:1.37=jmh,jmhCompileClasspath,jmhRuntimeClasspath
67+
org.opentest4j:opentest4j:1.3.0=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
6268
org.ow2.asm:asm-analysis:9.10.1=spotbugs
6369
org.ow2.asm:asm-commons:9.10.1=jacocoAnt,spotbugs
6470
org.ow2.asm:asm-tree:9.10.1=jacocoAnt,spotbugs
6571
org.ow2.asm:asm-util:9.10.1=spotbugs
72+
org.ow2.asm:asm:9.0=jmh,jmhCompileClasspath,jmhRuntimeClasspath
6673
org.ow2.asm:asm:9.10.1=jacocoAnt,spotbugs
67-
org.slf4j:jcl-over-slf4j:1.7.30=testCompileClasspath,testRuntimeClasspath
68-
org.slf4j:jul-to-slf4j:1.7.30=testCompileClasspath,testRuntimeClasspath
69-
org.slf4j:log4j-over-slf4j:1.7.30=testCompileClasspath,testRuntimeClasspath
74+
org.slf4j:jcl-over-slf4j:1.7.30=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
75+
org.slf4j:jul-to-slf4j:1.7.30=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
76+
org.slf4j:log4j-over-slf4j:1.7.30=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
7077
org.slf4j:slf4j-api:2.0.17=spotbugsSlf4j
71-
org.slf4j:slf4j-api:2.0.18=compileClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath
78+
org.slf4j:slf4j-api:2.0.18=compileClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath
7279
org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j
73-
org.spockframework:spock-bom:2.4-groovy-3.0=testCompileClasspath,testRuntimeClasspath
74-
org.spockframework:spock-core:2.4-groovy-3.0=testCompileClasspath,testRuntimeClasspath
75-
org.tabletest:tabletest-junit:1.2.2=testCompileClasspath,testRuntimeClasspath
76-
org.tabletest:tabletest-parser:1.2.1=testCompileClasspath,testRuntimeClasspath
80+
org.spockframework:spock-bom:2.4-groovy-3.0=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
81+
org.spockframework:spock-core:2.4-groovy-3.0=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
82+
org.tabletest:tabletest-junit:1.2.2=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
83+
org.tabletest:tabletest-parser:1.2.1=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
7784
org.xmlresolver:xmlresolver:5.3.3=spotbugs
78-
empty=annotationProcessor,spotbugsPlugins,testAnnotationProcessor
85+
empty=annotationProcessor,jmhAnnotationProcessor,spotbugsPlugins,testAnnotationProcessor
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.datadog.openfeature.internal;
2+
3+
import com.datadog.openfeature.internal.ufc.ServerConfiguration;
4+
import java.util.function.Consumer;
5+
6+
/** Delivers flag configurations to the runtime. */
7+
public interface ConfigurationService extends AutoCloseable {
8+
/**
9+
* Starts delivering configurations. Implementations may deliver the first configuration before
10+
* returning.
11+
*
12+
* @param listener the listener receiving configurations, or {@code null} when removed.
13+
*/
14+
void start(Consumer<ServerConfiguration> listener);
15+
16+
@Override
17+
void close();
18+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package com.datadog.openfeature.internal;
2+
3+
import java.util.concurrent.ConcurrentLinkedQueue;
4+
import java.util.concurrent.TimeUnit;
5+
import java.util.concurrent.atomic.AtomicInteger;
6+
import java.util.concurrent.locks.LockSupport;
7+
import java.util.function.Consumer;
8+
9+
/**
10+
* A bounded, lock-free, multiple-producer single-consumer queue. Producers never block; the
11+
* consumer polls with a timeout by parking, as producers do not signal it.
12+
*
13+
* @param <E> the element type.
14+
*/
15+
public final class EventQueue<E> {
16+
private static final long PARK_NANOS = TimeUnit.MILLISECONDS.toNanos(10);
17+
18+
private final ConcurrentLinkedQueue<E> queue = new ConcurrentLinkedQueue<>();
19+
private final AtomicInteger size = new AtomicInteger();
20+
private final int capacity;
21+
22+
public EventQueue(final int capacity) {
23+
this.capacity = capacity;
24+
}
25+
26+
/**
27+
* Offers an element without blocking.
28+
*
29+
* @param element the element to offer.
30+
* @return {@code true} if the element was queued, {@code false} if the queue is full.
31+
*/
32+
public boolean offer(final E element) {
33+
if (this.size.incrementAndGet() > this.capacity) {
34+
this.size.decrementAndGet();
35+
return false;
36+
}
37+
this.queue.offer(element);
38+
return true;
39+
}
40+
41+
/**
42+
* @return the head element, or {@code null} if the queue is empty.
43+
*/
44+
public E poll() {
45+
final E element = this.queue.poll();
46+
if (element != null) {
47+
this.size.decrementAndGet();
48+
}
49+
return element;
50+
}
51+
52+
/**
53+
* Polls the head element, waiting up to the given timeout for one to be available.
54+
*
55+
* @param timeout the maximum time to wait.
56+
* @param unit the timeout unit.
57+
* @return the head element, or {@code null} if none was available in time.
58+
* @throws InterruptedException if the consumer thread is interrupted while waiting.
59+
*/
60+
public E poll(final long timeout, final TimeUnit unit) throws InterruptedException {
61+
final long deadline = System.nanoTime() + unit.toNanos(timeout);
62+
while (true) {
63+
final E element = poll();
64+
if (element != null) {
65+
return element;
66+
}
67+
final long remaining = deadline - System.nanoTime();
68+
if (remaining <= 0) {
69+
return null;
70+
}
71+
LockSupport.parkNanos(this, Math.min(remaining, PARK_NANOS));
72+
if (Thread.interrupted()) {
73+
throw new InterruptedException();
74+
}
75+
}
76+
}
77+
78+
/**
79+
* Drains up to {@code limit} elements to the consumer.
80+
*
81+
* @param consumer the consumer of the drained elements.
82+
* @param limit the maximum number of elements to drain.
83+
*/
84+
public void drain(final Consumer<E> consumer, final int limit) {
85+
for (int i = 0; i < limit; i++) {
86+
final E element = poll();
87+
if (element == null) {
88+
return;
89+
}
90+
consumer.accept(element);
91+
}
92+
}
93+
94+
/**
95+
* @return the current number of queued elements.
96+
*/
97+
public int size() {
98+
return this.size.get();
99+
}
100+
101+
/**
102+
* @return the queue capacity.
103+
*/
104+
public int capacity() {
105+
return this.capacity;
106+
}
107+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package com.datadog.openfeature.internal;
2+
3+
import com.fasterxml.jackson.core.JsonEncoding;
4+
import com.fasterxml.jackson.core.JsonFactory;
5+
import com.fasterxml.jackson.core.JsonGenerator;
6+
import java.io.ByteArrayOutputStream;
7+
import java.io.IOException;
8+
import java.io.UncheckedIOException;
9+
import java.util.Collection;
10+
import java.util.Map;
11+
import javax.annotation.Nullable;
12+
13+
/** JSON serialization helpers for event payloads. */
14+
public final class JsonWriting {
15+
private static final JsonFactory JSON_FACTORY = new JsonFactory();
16+
17+
private JsonWriting() {}
18+
19+
/**
20+
* Serializes a JSON document.
21+
*
22+
* @param writer the document writer.
23+
* @return the UTF-8 encoded document.
24+
*/
25+
public static byte[] write(final Writer writer) {
26+
final ByteArrayOutputStream output = new ByteArrayOutputStream(256);
27+
try (JsonGenerator generator = JSON_FACTORY.createGenerator(output, JsonEncoding.UTF8)) {
28+
writer.write(generator);
29+
} catch (final IOException e) {
30+
throw new UncheckedIOException(e);
31+
}
32+
return output.toByteArray();
33+
}
34+
35+
/**
36+
* Writes an arbitrary value: maps, collections, strings, numbers, booleans and {@code null}.
37+
* Other values are written with their string representation.
38+
*
39+
* @param generator the generator to write to.
40+
* @param value the value to write.
41+
* @throws IOException if the value could not be written.
42+
*/
43+
public static void writeValue(final JsonGenerator generator, @Nullable final Object value)
44+
throws IOException {
45+
if (value == null) {
46+
generator.writeNull();
47+
} else if (value instanceof String) {
48+
generator.writeString((String) value);
49+
} else if (value instanceof Boolean) {
50+
generator.writeBoolean((Boolean) value);
51+
} else if (value instanceof Integer || value instanceof Long || value instanceof Short) {
52+
generator.writeNumber(((Number) value).longValue());
53+
} else if (value instanceof Number) {
54+
generator.writeNumber(((Number) value).doubleValue());
55+
} else if (value instanceof Map) {
56+
generator.writeStartObject();
57+
for (final Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet()) {
58+
generator.writeFieldName(String.valueOf(entry.getKey()));
59+
writeValue(generator, entry.getValue());
60+
}
61+
generator.writeEndObject();
62+
} else if (value instanceof Collection) {
63+
generator.writeStartArray();
64+
for (final Object element : (Collection<?>) value) {
65+
writeValue(generator, element);
66+
}
67+
generator.writeEndArray();
68+
} else {
69+
generator.writeString(value.toString());
70+
}
71+
}
72+
73+
/**
74+
* Writes a {@code {"key": ...}} object field, skipped when the key is {@code null}.
75+
*
76+
* @param generator the generator to write to.
77+
* @param fieldName the field name.
78+
* @param key the key value.
79+
* @throws IOException if the field could not be written.
80+
*/
81+
public static void writeKeyObject(
82+
final JsonGenerator generator, final String fieldName, @Nullable final String key)
83+
throws IOException {
84+
if (key != null) {
85+
generator.writeObjectFieldStart(fieldName);
86+
generator.writeStringField("key", key);
87+
generator.writeEndObject();
88+
}
89+
}
90+
91+
/** Writes a JSON document. */
92+
@FunctionalInterface
93+
public interface Writer {
94+
void write(JsonGenerator generator) throws IOException;
95+
}
96+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.datadog.openfeature.internal;
2+
3+
import com.datadog.openfeature.internal.connector.HealthMetrics;
4+
import javax.annotation.Nullable;
5+
6+
/** Thread creation and health metrics shared by the runtime components. */
7+
public final class RuntimeServices {
8+
private final HealthMetrics healthMetrics;
9+
10+
public RuntimeServices(final HealthMetrics healthMetrics) {
11+
this.healthMetrics = healthMetrics;
12+
}
13+
14+
/**
15+
* Creates a daemon thread.
16+
*
17+
* @param role the thread role, used to name the thread.
18+
* @param task the thread task.
19+
* @return the created thread, not started yet.
20+
*/
21+
public Thread newThread(final String role, final Runnable task) {
22+
final Thread thread = new Thread(task, "dd-openfeature-" + role);
23+
thread.setDaemon(true);
24+
thread.setContextClassLoader(null);
25+
return thread;
26+
}
27+
28+
/**
29+
* Increments a health counter, skipping non-positive values.
30+
*
31+
* @param name the metric name.
32+
* @param value the increment.
33+
* @param reason the optional {@code reason} tag value.
34+
*/
35+
public void countMetric(final String name, final long value, @Nullable final String reason) {
36+
if (value > 0) {
37+
this.healthMetrics.count(name, value, reason);
38+
}
39+
}
40+
}

0 commit comments

Comments
 (0)