A light-weight application framework to build high performant and distributed java applications.
| Module | Description |
|---|---|
| oap-application | IoC/DI Kernel — discovers services from HOCON descriptors, wires dependencies, manages start/stop lifecycle |
| oap-stdlib | Core utilities — Binder (JSON/HOCON/YAML/XML), Files, IoStreams, Cuid, Dates, Stream, Result |
| oap-http | Undertow-based HTTP server with named ports, PNIO high-performance pipeline, and HTTP client |
| oap-ws | Annotation-driven web services (@WsMethod, @WsParam) with session and interceptor support |
| oap-jpath | JPath expression language for navigating object graphs: ${var.field.method().array[n]} |
| oap-formats | Template engine, TSV/CSV parsing, JSON schema validation, and log streaming |
| oap-statsdb | Distributed in-memory statistics tree with hierarchical rollup and MongoDB persistence |
| oap-message | Reliable binary message delivery with disk spill, retry, and MD5-based deduplication |
| oap-storage | In-memory object store (MemoryStorage) with MongoDB sync and cloud object storage |
| oap-highload | CPU affinity utility for pinning threads to specific CPU cores |
| oap-mail | Email sending via SMTP and SendGrid with a persistent delivery queue |
| oap-maven-plugin | Build-time code generation: startup scripts and dictionary enum source files |
| Guide | Description |
|---|---|
| Ext — Pluggable Field Extensions | Attach pluggable typed sub-objects to bean fields via oap.json.ext.Ext; covers JSON deserialization and template engine integration |
| Testing | Fixture lifecycle, KernelFixture, assertion helpers, MongoDB/S3 mocks, and benchmark harness |
| Project | Description |
|---|---|
| oap-config | HOCON-based configuration library used by the Kernel to parse oap-module.oap / application.conf |
| oap-config-plugin | IntelliJ IDEA plugin: HOCON language support (syntax highlighting, references) |
| oap-application-plugin | IntelliJ IDEA plugin: navigation/completion for OAP Kernel service definitions |
The IoC/DI kernel for the OAP framework. Kernel discovers service descriptors from every jar on the classpath, builds a dependency graph, instantiates and wires services, and manages their full lifecycle (start, scheduled runs, stop).
Services are plain Java classes — no framework annotations required. Everything is declared in HOCON files.
- Overview
- Module Declaration (oap-module.oap)
- Application Configuration (application.conf)
- Reference Syntax
- Supervision and Service Lifecycle
- Lifecycle Annotations
- Dependency Injection Mechanics
- Abstract Services
- Module Discovery
- Kernel API
- KernelExt: Service Metadata Extensions
- Testing with KernelFixture
- Production Boot
- Error Reference
Startup sequence:
1. Scan classpath for all META-INF/oap-module.oap files
2. Load and validate each module descriptor
3. Read application.conf (+ conf.d/ + CONFIG.* env vars)
4. Select modules reachable via boot.main transitive dependsOn graph
5. Topological sort modules and services
6. Instantiate each service (constructor or field injection)
7. Wire references, listeners, and links
8. Start supervised services via Supervisor
Boot is the production entry point. KernelFixture is the test entry point.
Every OAP jar ships a descriptor at src/main/resources/META-INF/oap-module.oap.
| Field | Type | Default | Meaning |
|---|---|---|---|
name |
String | required | Unique module identifier; must match [A-Za-z\-_0-9]+ |
enabled |
boolean | true |
Disable the entire module and all its services |
dependsOn |
list<String> | [] |
Module-level ordering: this module starts after listed modules |
services |
map | required | Map of service name → service block |
| Field | Type | Default | Meaning |
|---|---|---|---|
implementation |
String | required | Fully-qualified class name |
abstract |
boolean | false |
Marks an interface/abstract-class slot; must be filled at deployment via application.conf |
default |
reference | — | Default concrete implementation used when abstract=true and nothing is specified in application.conf |
enabled |
boolean | true |
Disable this service individually |
parameters |
map | {} |
Constructor or field values; may contain <...> references |
supervision |
block | — | Lifecycle management (see Supervision) |
dependsOn |
list | [] |
Explicit start-order hints within a module |
listen |
map | {} |
Listener registration: listenerName = <ref> calls ref.addListenerNameListener(this) |
link |
map | {} |
Reverse wiring: fieldName = <ref> calls ref.addFieldName(this) / ref.setFieldName(this) / appends to collection |
The
serviceandserviceskeys are aliases for theservicesmap.
m1.oap
name = m1
services {
cm {
implementation = com.example.ComplexMap
}
ServiceOneP1 {
implementation = com.example.ServiceOne
parameters {
i = 2ms
kernel = <kernel.self>
complexMap = <modules.this.cm>
complex {
i = 2
map.a.i = 1
}
complexes = [{i = 2}]
}
supervision.delay = 5ms
}
}m2.oap
name = m2
dependsOn = m1
services {
ServiceTwo {
implementation = com.example.ServiceTwo
parameters {
j = 1
one = <modules.m1.ServiceOneP1>
}
listen.some = <modules.m1.ServiceOneP1>
supervision.supervise = true
}
ServiceScheduled {
implementation = com.example.ServiceScheduled
supervision {
schedule = true
delay = 1s
}
}
}m3.oap (list parameter referencing services from two modules)
name = m3
dependsOn = [m1, m2]
services {
ServiceDepsList {
implementation = com.example.ServiceDepsList
parameters {
deps = [
<modules.m1.ServiceOneP1>
<modules.m2.ServiceTwo>
]
}
}
}application.conf selects which modules activate and overrides their parameters at deployment time.
boot.main = [m1, m2, m3] # one or more module names
shutdown {
serviceTimeout = 5s # warn timeout per service during stop
serviceAsyncShutdownAfterTimeout = false # continue stopping if timeout exceeded
}
services {
# Override a parameter
m1.ServiceOneP1.parameters.i = 100ms
# Disable a service
m2.ServiceTwo.enabled = false
# Assign a concrete implementation to an abstract service slot
my-module.my-abstract-service = <modules.impl-module.ConcreteImpl>
}All *.conf and *.yaml files in conf.d/ are merged with application.conf. The default conf.d path is <application.conf parent>/conf.d. Useful for splitting deployment-specific values across files.
# conf.d/ports.conf
services.my-module.my-service.parameters.port = 9090
# conf.d/db.yaml
services:
my-module:
db-service:
parameters:
url: jdbc:postgresql://localhost/mydb
Any environment variable starting with CONFIG. is stripped of the prefix and injected as a HOCON key. This allows per-deployment overrides without modifying config files.
export CONFIG.services.my-module.my-service.enabled=false
export CONFIG.services.my-module.my-service.parameters.val='"hello"'kernel.start( Map.of(
"boot.main", "m1",
"services.m1.ServiceOneP1.parameters.i", "50"
) );<...> expressions in parameter values are resolved by the kernel before service construction.
| Expression | Resolves to |
|---|---|
<modules.moduleName.serviceName> |
The live instance of the named service |
<modules.this.serviceName> |
A service in the same module |
<modules.self.serviceName> |
Alias for this |
<modules.*.serviceName> |
First matching service across all modules |
<kernel.self> |
The Kernel instance itself |
<services.self.name> |
The string name of the current service |
location.module |
The URL of the module's own .oap file |
References work in parameters, list parameters, and map parameters.
# Inject the kernel itself
parameters.kernel = <kernel.self>
# Cross-module reference
parameters.server = <modules.oap-http.oap-http-server>
# Same-module reference
parameters.cache = <modules.this.cache-service>
# Wildcard: first service named "config" in any module
parameters.config = <modules.*.config>
# Service's own registered name
parameters.serviceName = <services.self.name>The supervision block controls how the kernel starts, runs, and stops a service.
| Field | Type | Default | Effect |
|---|---|---|---|
supervise |
boolean | false |
Call lifecycle methods on start/stop |
thread |
boolean | false |
Run service as a Runnable in a dedicated daemon thread |
schedule |
boolean | false |
Run service periodically (combine with delay or cron) |
delay |
duration | 0 |
Fixed-delay interval; supports HOCON duration units (1s, 5ms, 1h) |
cron |
String | — | Quartz cron expression for scheduled runs |
preStartWith |
list<String> | ["preStart"] |
Method names called before start |
startWith |
list<String> | ["start"] |
Method names called on start |
preStopWith |
list<String> | ["preStop"] |
Method names called before stop |
stopWith |
list<String> | ["stop","close"] |
Method names called on stop; services implementing Closeable get close() called automatically |
Missing lifecycle methods are silently skipped.
Supervised service — lifecycle methods called in order:
service {
implementation = com.example.MyService # has preStart/start/preStop/stop methods
supervision.supervise = true
}Result on start: preStart() → start()
Result on stop: preStop() → stop()
Supervised thread — runs Runnable.run() in a daemon thread:
thread {
implementation = com.example.WorkerService
supervision {
supervise = true
thread = true
}
}Delay-scheduled — runs Runnable.run() every N ms with fixed delay:
poller {
implementation = com.example.PollerService
supervision {
schedule = true
delay = 30s
}
}Cron-scheduled — runs Runnable.run() on a Quartz cron schedule:
nightly-cleanup {
implementation = com.example.CleanupJob
supervision {
supervise = true
schedule = true
cron = "0 0 2 * * ? *" # every day at 02:00 UTC
}
}When supervise = true is combined with schedule = true, lifecycle methods are called around the entire scheduler lifetime (start before first run, stop after last run).
On kernel.stop(), the Supervisor stops services in reverse registration order:
- Threads and scheduled tasks are interrupted/cancelled.
- Supervised services have
preStop()thenstop()(orclose()) called.
shutdown.serviceTimeout (default 5s) is the warn threshold per service. Set shutdown.serviceAsyncShutdownAfterTimeout = true to continue shutdown after a timeout rather than waiting.
As an alternative to relying on method names (preStart, start, etc.), lifecycle hooks can be declared with annotations from the oap.application.annotation package. The kernel discovers annotated methods at startup regardless of their name.
| Annotation | Phase | Equivalent supervision field |
|---|---|---|
@PreStart |
Before service start | preStartWith |
@Start |
Service start | startWith |
@PreStop |
Before service stop | preStopWith |
@Stop |
Service stop | stopWith |
Annotations and name-based discovery are independent — both are applied. An annotated method named start is invoked by both paths; an annotated method with any other name is invoked only via the annotation.
import oap.application.annotation.PreStart;
import oap.application.annotation.Start;
import oap.application.annotation.PreStop;
import oap.application.annotation.Stop;
public class MyService {
@PreStart
public void onBeforeStart() { /* runs before start */ }
@Start
public void onStart() { /* runs on start */ }
@PreStop
public void onBeforeStop() { /* runs before stop */ }
@Stop
public void onStop() { /* runs on stop */ }
}The service still needs supervision.supervise = true in its oap-module.oap declaration.
The kernel reflects on the service class and calls a constructor whose parameter names match keys in parameters. References are resolved first, then scalars are type-coerced.
public class ServiceTwo {
public ServiceTwo( ServiceOne one, int j ) { ... }
}ServiceTwo {
implementation = com.example.ServiceTwo
parameters {
one = <modules.m1.ServiceOneP1>
j = 42
}
}Parameters not consumed by the constructor are applied to public fields by name.
Maps in parameters are bound to nested objects:
parameters.complex {
i = 2
map.a.i = 1
}listen.name = <ref> — after construction, calls ref.addNameListener(this). The target service must have a method addNameListener(T listener).
ServiceTwo {
listen.some = <modules.m1.ServiceOneP1> # calls ServiceOneP1.addSomeListener(serviceTwo)
}link.name = <ref> — after construction, registers this on the target service. The kernel attempts in order:
ref.addName(this)ref.setName(this)ref.addNameListener(this)ref.namefield (appends if collection, sets otherwise)
ti1 {
implementation = com.example.Impl
link.registry = <modules.this.service-registry>
}A disabled service referenced via <modules...> resolves to null and is omitted from list parameters. No error is thrown.
Cyclic module or service dependencies are detected at startup and throw ApplicationException("cyclic dependency detected").
The abstract service pattern defines an interface slot in a module that must be filled with a concrete implementation — either by a default fallback or by an explicit assignment in application.conf.
# oap-module.oap
name = my-module
services {
abstract-service {
abstract = true
implementation = com.example.AbstractService # interface or abstract class
default = <modules.my-module.default-impl> # optional fallback
}
service {
implementation = com.example.Container
parameters {
dep = <modules.this.abstract-service>
fieldParam = <modules.this.abstract-service>
listParam = [<modules.this.abstract-service>]
}
}
default-impl {
implementation = com.example.DefaultImpl
}
}# application.conf
boot.main = my-module
services {
my-module.abstract-service = <modules.my-module.default-impl>
}Create a test module that dependsOn the production module, disable the default implementation, and point the abstract service at the mock:
# test-module.oap
name = my-module-test
dependsOn = my-module
services {
mock {
implementation = com.example.MockImpl
}
}# application-test.conf
boot.main = my-module-test
services {
my-module {
default-impl.enabled = false
abstract-service = <modules.my-module-test.mock>
}
}abstract = truenot set butimplementationis an interface →ApplicationException: "abstract = true" property is missing- No concrete implementation registered and no default →
ApplicationException: No implementation has been declared - Implementations exist but none selected →
ApplicationException: No implementation specified ... Available implementations [...]
Module.CONFIGURATION.urlsFromClassPath() scans all jars on the classpath for module descriptors in priority order:
META-INF/oap-module.oapMETA-INF/oap-module.confMETA-INF/oap-module.yaml/.ymlMETA-INF/oap-module.json
All discovered modules are loaded. Only modules reachable via boot.main's transitive dependsOn graph are activated.
Module and service names must match the pattern ^[A-Za-z\-_0-9]++$.
With boot.main = [m1] and the graph m1 → m3 → m4, modules m1, m3, and m4 are activated. Module m2 (if unreachable from m1) is silently ignored.
Attach typed metadata to service declarations by registering an extension type:
# META-INF/oap-module-ext.conf
services.ws.implementation = com.example.WsServiceExt
Then use the extension key freely in any module's service block (see KernelExt).
// Use all module descriptors from the classpath
Kernel kernel = new Kernel( Module.CONFIGURATION.urlsFromClassPath() );
// Named kernel (name appears in logs)
Kernel kernel = new Kernel( "my-app", Module.CONFIGURATION.urlsFromClassPath() );kernel.start( Path.of( "/etc/myapp/application.conf" ) );
kernel.start( Path.of( "/etc/myapp/application.conf" ), Path.of( "/etc/myapp/conf.d" ) );
kernel.start( "classpath:application.conf", "conf.d" );
kernel.start( Map.of( "boot.main", "my-module" ) ); // programmatic (tests)
kernel.start( applicationConfiguration ); // pre-built config object// Exact module + name
Optional<MyService> s = kernel.service( "my-module", "my-service" );
// By reference string
Optional<MyService> s = kernel.service( "my-module.my-service" );
Optional<MyService> s = kernel.service( "<modules.my-module.my-service>" );
// All services with a given name across all modules (use "*" for any module)
List<MyService> list = kernel.services( "*", "my-service" );
// By class
List<MyService> all = kernel.ofClass( MyService.class );
Optional<MyService> first = kernel.serviceOfClass( MyService.class );
MyService req = kernel.serviceOfClass2( MyService.class ); // throws if not found
// Scoped to a module
List<MyService> list = kernel.ofClass( "my-module", MyService.class );
// By extension key (see KernelExt section)
List<ServiceExt<WsConfig>> wsServices = kernel.servicesByExt( "ws" );kernel.stop();
// Kernel implements Closeable — use try-with-resources in tests
try ( Kernel kernel = new Kernel( Module.CONFIGURATION.urlsFromClassPath() ) ) {
kernel.start( Map.of( "boot.main", "my-module" ) );
// assertions
}Arbitrary typed metadata can be attached to service declarations and queried at runtime. This is the mechanism used by oap-ws to discover HTTP-annotated services without scanning all services by type.
Add a line to META-INF/oap-module-ext.conf mapping the extension key to a Java class:
services.ws.implementation = com.example.WsServiceExt
WsServiceExt is a plain POJO that the HOCON binder will populate.
services {
my-api {
implementation = com.example.MyApi
ws {
path = /api/v1
port = httpprivate
}
}
}List<ServiceExt<WsServiceExt>> endpoints = kernel.servicesByExt( "ws" );
for ( ServiceExt<WsServiceExt> ep : endpoints ) {
System.out.println( ep.name + " → " + ep.ext.path );
}KernelFixture (from oap-application-test) is a TestNG fixture that starts a real Kernel before each test method and stops it after.
| Variable | Value |
|---|---|
TEST_HTTP_PORT |
A free HTTP port allocated for the test |
TEST_DIRECTORY |
A per-test temp directory |
TEST_RESOURCE_PATH |
Path to the test's resource directory |
TEST_HTTP_PREFIX |
http://localhost:${TEST_HTTP_PORT} |
@Listeners( Fixtures.class )
public class MyServiceTest {
private final TestDirectoryFixture testDirectory = fixture( new TestDirectoryFixture() );
private final KernelFixture kernel = fixture( new KernelFixture(
testDirectory,
Resources.url( MyServiceTest.class, "application.test.conf" ).orElseThrow(),
List.of( Resources.url( MyServiceTest.class, "oap-module.oap" ).orElseThrow() )
) );
@Test
public void myTest() {
MyService svc = kernel.service( "*", MyService.class ).orElseThrow();
// test assertions
}
}boot.main = my-module
services {
my-module {
my-service.parameters.port = ${TEST_HTTP_PORT}
my-service.parameters.dir = ${TEST_DIRECTORY}
}
}new KernelFixture( testDir, confUrl )
.withProperties( Map.of( "MY_KEY", "value" ) ) // inject HOCON substitution vars
.withConfResource( MyTest.class, "extra.conf" ) // merge extra conf file
.withConfdResources( MyTest.class, "conf.d" ); // add conf.d directoryFor tests not using the TestNG fixture machinery:
try ( Kernel kernel = new Kernel( Module.CONFIGURATION.urlsFromClassPath() ) ) {
kernel.start( Map.of( "boot.main", "my-module" ) );
MyService svc = kernel.serviceOfClass2( MyService.class );
// assertions
}Boot.main is the production entry point. It creates a Kernel from all classpath module descriptors and starts it.
java -cp <classpath> oap.application.Boot start \
--config /etc/myapp/application.conf \
--config-directory /etc/myapp/conf.d # optional; defaults to <config parent>/conf.dSIGINT and SIGTERM both trigger a graceful kernel.stop() followed by System.exit(0).
Boot.terminated is a public volatile boolean that becomes true when shutdown begins. Useful for polling in application-level shutdown hooks.
| Message | Cause |
|---|---|
boot.main must contain at least one module name |
boot.main is empty or missing in application.conf |
<url>: module.name is blank |
A module file has no name field |
unknown application configuration module: X |
application.conf references a module not found on the classpath |
unknown application configuration services: M.[S] |
application.conf overrides a service that does not exist in module M |
main.boot: unknown module name 'X' |
boot.main names a module not found on the classpath |
module name X does not match ... |
Module name contains characters outside [A-Za-z\-_0-9] |
service name X does not match ... |
Service name contains illegal characters |
failed to initialize service: M:S. implementation == null |
Service block has no implementation field |
[M:*] dependencies are not enabled |
Module's dependsOn target is disabled |
[M:S] dependencies are not enabled. Required service [X] is disabled |
A service parameter references a disabled service |
cyclic dependency detected |
Module-level dependency cycle |
services cyclic dependency detected |
Service-level dependency cycle |
No implementation has been declared for the abstract service <M.S> |
abstract=true with no concrete impl registered and no default |
No implementation specified for abstract service <M.S> ... Available implementations [...] |
Concrete impls exist but none was selected in application.conf |
Service <M.S> has an abstract implementation, but the "abstract = true" property is missing |
Interface/abstract class used without abstract = true |
Unknown service X in reference <modules.M.X> |
Abstract service default or application.conf assignment references a non-existent service |
M:S Service X is already registered |
Two enabled services in the same module have the same name |
for S listening object <ref> is not found |
listen reference does not resolve to a known service |
listener L should have method addLListener in <ref> |
listen.L target has no addLListener(T) method |
Core utility library for the OAP platform. Provides serialization, reflection, file I/O, collections, concurrency primitives, and identifier abstractions used across all OAP modules.
| Package | Contents |
|---|---|
oap.json |
Binder — JSON/HOCON/YAML/XML/BSON serializer |
oap.reflect |
Reflect, Reflection, TypeRef, Coercions |
oap.id |
Identifier, StringIdentifier, IntIdentifier |
oap.io |
Files, IoStreams, Resources, ContentReader, ContentWriter |
oap.util |
Stream, Cuid, Dates, Result, Lists, Maps, Sets, Strings, Pair |
oap.concurrent |
Executors, Threads, Scheduler, Stopwatch, LimitedTimeExecutor |
oap.net |
Inet, IpRangeTree |
oap.dictionary |
Dictionary |
Pre-configured Jackson ObjectMapper wrappers. All instances are thread-safe singletons.
| Instance | Format | Notes |
|---|---|---|
Binder.json |
JSON | Standard serializer; skips nulls |
Binder.jsonWithTyping |
JSON | Embeds @class type info for polymorphic deserialization |
Binder.hocon |
HOCON | Resolves ${?ENV_VAR} and system properties |
Binder.hoconWithoutSystemProperties |
HOCON | No system property substitution |
Binder.yaml |
YAML | |
Binder.xml |
XML | |
Binder.xmlWithTyping |
XML | With type info |
Binder.bson |
BSON | For MongoDB codecs |
// Object → String
String json = Binder.json.marshal( order );
String pretty = Binder.json.marshalWithDefaultPrettyPrinter( order );
// Object → Path (auto-detects encoding from extension)
Binder.json.marshal( Path.of( "/data/order.json.gz" ), order );
// Streaming JSON array to OutputStream
Binder.json.marshal( outputStream, List.of( order1, order2 ) );Order order = Binder.json.unmarshal( Order.class, jsonString );
Order order = Binder.json.unmarshal( Order.class, path );
Order order = Binder.json.unmarshal( Order.class, url );
Order order = Binder.json.unmarshal( Order.class, inputStream );
// Generic types — use TypeRef
List<Order> orders = Binder.json.unmarshal( new TypeRef<List<Order>>() {}, jsonString );// Apply HOCON-format overrides to an existing object (preserves unmentioned fields)
Binder.update( order, Map.of( "status", "SHIPPED" ) );
Binder.update( order, "{ status: SHIPPED, total: 99.99 }" );// Parse HOCON with extra fallback properties merged in
Binder b = Binder.hoconWithConfig( Map.of( "host", "localhost", "port", 8080 ) );
MyConfig cfg = b.unmarshal( MyConfig.class, "classpath:config.conf" );Extra Jackson modules are registered by listing them in META-INF/jackson.modules (one class name per line). The default configuration:
- Field visibility:
ANY(no getters required) - Accepts case-insensitive property names
- Accepts single-quoted strings
- Skips null input values on deserialization
- Omits null fields on serialization
- Joda-Time, JDK8, JavaTime modules registered
Java generic type token. Use it anywhere a Class<T> cannot carry generic parameters.
TypeRef<List<Order>> ref = new TypeRef<List<Order>>() {};
List<Order> orders = Binder.json.unmarshal( ref, json );
// Also accepted by Reflect
Reflection r = Reflect.reflect( ref );OAP's cached reflection layer, built on Guava TypeToken.
Reflection r = Reflect.reflect( Order.class );
// Construct
Order order = r.newInstance();
Order order = r.newInstance( Map.of( "id", "o-1", "total", 42 ) );
// Fields
Reflection.Field f = r.field( "status" ).orElseThrow();
f.set( order, "SHIPPED" );
Object v = f.get( order );
// Methods
r.method( "validate" ).ifPresent( m -> m.invoke( order ) );
// Iterate all declared fields
r.fields.forEach( field -> System.out.println( field.name() + " : " + field.type() ) );Coercions.basic() is the default type-coercion registry (String→int, String→enum, etc.) used by the Kernel when wiring service parameters.
A constructor parameter can be omitted from the args map passed to newInstance if it is annotated @javax.annotation.Nullable or @com.fasterxml.jackson.annotation.JsonProperty(required = false) — it is then passed as null. All other parameters must still be present as keys in args.
public Order( String id, @Nullable String note ) { ... }
Order o = r.newInstance( Map.of( "id", "o-1" ) ); // note == null, no exception@JsonProperty("xxx") (or @JsonProperty(value = "xxx")) renames the lookup key for a constructor parameter — newInstance reads it from args.get("xxx") instead of the Java parameter name.
Note: JsonProperty.required() defaults to false, so a plain @JsonProperty("xxx") also makes the parameter optional (see above) — pass required = true if the aliased parameter must still be present in args.
public Order( @JsonProperty( value = "order_id", required = true ) String id ) { ... }
Order o = r.newInstance( Map.of( "order_id", "o-1" ) ); // id == "o-1"@JsonAlias({ "a", "b" }) adds further acceptable lookup keys on top of the parameter's existing name/@JsonProperty value — newInstance accepts args under any of them:
public Order( @JsonAlias( { "order_id", "orderId" } ) String id ) { ... }
r.newInstance( Map.of( "id", "o-1" ) ); // still matches (Java parameter name)
r.newInstance( Map.of( "order_id", "o-1" ) ); // also matches
r.newInstance( Map.of( "orderId", "o-1" ) ); // also matchesStrategy interface that extracts, generates, and converts the ID of a data object.
// Explicit getter+setter (most common)
Identifier<String, Order> id = Identifier
.forId( o -> o.id, ( o, newId ) -> o.id = newId )
.suggestion( o -> o.customerName ) // derive initial id from this field
.length( 10 ) // max generated id length
.build();
// Derive from a JPath expression
Identifier<String, Order> id = Identifier.<Order>forPath( "$.id" ).build();
// Use @Id annotation on the field
Identifier<String, Order> id = Identifier.<Order>forAnnotation().build();Identifier.generate(base, length, conflict, maxAttempts, options) — slug generator with deconfliction:
Option.COMPACT— removes vowels from the base (shorter slugs)Option.FILL— pads withXto reachlength
Static file system utilities.
// Read
String text = Files.readString( path );
String text = Files.readString( path, encoding );
byte[] bytes = Files.read( path, Encoding.GZIP, ContentReader.ofBytes() );
// Write
Files.writeString( path, Encoding.PLAIN, "hello" );
Files.writeString( path, Encoding.GZIP, "hello", /* append */ false );
// Glob matching (Ant-style wildcards)
List<Path> found = Files.wildcard( basePath, "**/*.json" );
List<Path> found = Files.wildcard( basePath, "logs/*.log", "logs/*.log.gz" );
// Directory operations
Files.ensureFile( path ); // creates parent directories; does not create the file
Files.ensureDirectory( path ); // creates the directory and all parents
Files.delete( path ); // recursive delete
Files.copyDirectory( src, dest );
Files.move( src, dest ); // atomic rename where possible
// Metadata
long ts = Files.getLastModifiedTime( path ); // epoch ms
boolean exists = Files.exists( path );
// Hashed subdirectory (distributes many files across a 3-level tree)
Path deep = Files.deepPath( basePath, filename );Stream I/O with transparent compression support.
Encoding.PLAIN // no compression
Encoding.GZIP // gzip
Encoding.BZIP2 // bzip2
Encoding.LZ4 // LZ4
Encoding.ZSTD // Zstandard
Encoding.ZIP // ZIP
// Auto-detect from path or URL extension
Encoding enc = Encoding.from( path );
Encoding enc = Encoding.from( url );InputStream in = IoStreams.in( path );
InputStream in = IoStreams.in( path, Encoding.GZIP );
Stream<String> lines = IoStreams.lines( path );
Stream<String> lines = IoStreams.lines( path, Encoding.GZIP );
Stream<String> lines = IoStreams.lines( url );
Stream<String> lines = IoStreams.lines( inputStream );OutputStream out = IoStreams.out( path );
OutputStream out = IoStreams.out( path, Encoding.GZIP );
OutputStream out = IoStreams.out( path, Encoding.GZIP, /* append */ true );
IoStreams.write( path, Encoding.GZIP, "text content" );
IoStreams.write( path, Encoding.GZIP, inputStream );
IoStreams.write( path, Encoding.PLAIN, lineStream ); // Stream<String>, one line per elementClasspath resource loading relative to a context class.
// Single resource
Optional<URL> url = Resources.url( MyClass.class, "config.conf" );
Optional<Path> path = Resources.filePath( MyClass.class, "config.conf" );
// All resources with this name across all jars (useful for META-INF aggregation)
List<URL> urls = Resources.urls( "META-INF/services/MyService" );
List<Path> paths = Resources.filePaths( MyClass.class, "schemas" );
// Read content
Optional<String> text = Resources.read( MyClass.class, "query.sql", ContentReader.ofString() );
Stream<String> lines = Resources.lines( "META-INF/jackson.modules" );Typed I/O adapters passed to Files.read(), IoStreams.write(), and cloud storage APIs.
| Factory | Returns |
|---|---|
ContentReader.ofString() |
String (UTF-8) |
ContentReader.ofBytes() |
byte[] |
ContentReader.ofLines() |
List<String> |
ContentReader.ofLinesStream() |
Stream<String> |
ContentReader.ofInputStream() |
InputStream (caller must close) |
Chain readers with .andThen(fn):
ContentReader<MyObj> r = ContentReader.ofString()
.andThen( s -> Binder.json.unmarshal( MyObj.class, s ) );| Factory | Writes |
|---|---|
ContentWriter.ofString() |
String → UTF-8 bytes |
ContentWriter.ofBytes() |
byte[] pass-through |
ContentWriter.ofJson() |
Any object → JSON bytes via Binder.json |
ContentWriter.ofObject() |
Java serialization (ObjectOutputStream) |
Cluster-unique identifier — time-based, monotonic, embeds the local IP address.
// Production: globally unique, embeds timestamp + local IP
String id = Cuid.UNIQUE.next(); // e.g. "0000018F3A2B1C00C0A80101"
long idL = Cuid.UNIQUE.nextLong();
String last = Cuid.UNIQUE.last(); // last generated (no increment)
// Parse a Cuid back to components
Cuid.UniqueCuid.Info info = Cuid.UniqueCuid.parse( id );
// info.time → DateTime (UTC)
// info.ip → int[4]
// info.count → per-millisecond counter
// Tests: deterministic counter starting at seed
Cuid counter = Cuid.incremental( 1 );
counter.next(); // "1"
counter.next(); // "2"Joda-Time utilities. All operations use UTC unless otherwise noted.
| Constant | Pattern | Example |
|---|---|---|
Dates.FORMAT_MILLIS |
yyyy-MM-dd'T'HH:mm:ss.SSS |
2024-06-01T14:30:00.000 |
Dates.FORMAT_SIMPLE |
yyyy-MM-dd'T'HH:mm:ss |
2024-06-01T14:30:00 |
Dates.FORMAT_DATE |
yyyy-MM-dd |
2024-06-01 |
String s = Dates.formatDateWithMillis( DateTime.now() );
String s = Dates.FORMAT_DATE.print( dt );
Result<DateTime, Exception> r = Dates.parseDateWithMillis( "2024-06-01T14:30:00.000" );
Result<DateTime, Exception> r = Dates.parseDate( "2024-06-01T14:30:00" );
DateTime now = Dates.nowUtc();
DateTime today = Dates.nowUtcDate(); // time zeroed to 00:00:00.000Dates.s( 30 ) // 30 seconds in ms
Dates.m( 5 ) // 5 minutes in ms
Dates.h( 2 ) // 2 hours in ms
Dates.d( 7 ) // 7 days in ms
Dates.w( 2 ) // 2 weeks in ms
String human = Dates.durationToString( Dates.h(1) + Dates.m(30) ); // "1h 30m"Dates.setTimeFixed( 2024, 6, 1, 14, 30, 0 ); // freeze at 14:30:00 UTC
Dates.incFixed( Dates.h( 1 ) ); // advance by 1 hour
DateTimeUtils.setCurrentMillisSystem(); // restore real clockOAP's extended stream — wraps java.util.stream.Stream and adds extra operations.
// Factory methods
Stream<T> s = Stream.of( collection );
Stream<T> s = Stream.of( iterator );
Stream<T> s = Stream.of( enumeration );
Stream<T> s = Stream.traverse( initialState, nextFn ); // iterator-style generator
// Extra intermediates
stream.takeWhile( predicate ) // stop at first non-matching element
stream.grouped( batchSize ) // → Stream<List<E>> in fixed-size batches
stream.grouped( classifier ) // → BiStream<K, List<E>> grouped by key
stream.zip( otherStream, zipper ) // pair-wise transform into a new type
stream.zip( otherStream ) // → BiStream<E, B>
// Extra terminals
List<E> list = stream.toList();
Set<E> set = stream.toSet();
Map<K,V> map = stream.toMap( keyFn, valueFn );Static utility classes.
// Lists
List<B> mapped = Lists.map( list, fn );
List<T> filtered = Lists.filter( list, pred );
List<T> concat = Lists.concat( listA, listB );
List<T> reversed = Lists.reverse( list );
Optional<T> head = Lists.head( list );
// Maps
Map<K,V> filtered = Maps.filter( map, ( k, v ) -> pred );
List<R> asList = Maps.toList( map, ( k, v ) -> ... );
LinkedHashMap<K,V> linked = Maps.toLinkedHashMap( list, keyFn, valueFn );
// Sets
Set<T> intersection = Sets.intersection( setA, setB );
Set<T> union = Sets.union( setA, setB );
Set<T> difference = Sets.difference( setA, setB );
// Strings
String result = Strings.substitute( "Hello ${name}!", Map.of( "name", "World" ) );
String sorted = Strings.sortLines( multilineString );
byte[] bytes = Strings.toByteArray( str );
String hex = Strings.toHexString( bytes );Typed success/failure without exceptions.
Result<Order, String> r = Result.success( order );
Result<Order, String> r = Result.failure( "not found" );
// Wrap a throwing supplier — catches all Throwable
Result<Order, Throwable> r = Result.catching( () -> orderService.find( id ) );
// Query
boolean ok = r.isSuccess();
Order order = r.successValue;
String reason = r.failureValue;
// Transform
Result<String, String> r2 = r.mapSuccess( o -> o.id );
Result<Order, Throwable> r3 = r.mapFailure( msg -> new RuntimeException( msg ) );
// Branch
r.ifSuccess( o -> log.info( "ok: {}", o.id ) )
.ifFailure( e -> log.warn( "failed: {}", e ) );
// Terminate
Optional<Order> opt = r.toOptional();
Order order = r.orElse( defaultOrder );
Order order = r.orElseThrow( msg -> new RuntimeException( msg ) );Optional<InetAddress> ip = Inet.getLocalIp();
String host = Inet.hostName();// Named scheduled thread pool
ScheduledExecutorService exec = Executors.newScheduledThreadPool( 4, "my-service" );
// Named single-thread executor
ExecutorService exec = Executors.newSingleThreadExecutor( "my-worker" );Thread names include the pool name for easy identification in thread dumps and profilers.
HTTP server and client infrastructure for the OAP platform. Provides an Undertow-based server with named ports, a high-performance non-blocking pipeline, Prometheus metrics exporters, and test utilities.
| Module | Description | Depends on |
|---|---|---|
| oap-http | NioHttpServer, OapHttpClient, HealthHttpHandler, HttpServerExchange |
— |
| oap-pnio-v3 | High-performance non-blocking pipeline (PnioHttpHandler, PnioExchange) |
oap-http |
| oap-http-prometheus | Prometheus scrape endpoint, JVM metrics, application info exporter | oap-http |
| oap-http-test | HttpAsserts, HttpServerExchangeStub, MockHttpContext |
oap-http |
1. Add oap-http to your module's dependsOn:
name = my-module
dependsOn = [oap-http]2. Reference the server and bind a handler:
services {
my-handler {
implementation = com.example.MyHandler
parameters {
server = <modules.oap-http.oap-http-server>
}
}
}public class MyHandler implements HttpHandler {
public MyHandler( NioHttpServer server ) {
server.bind( "/api/hello", this );
}
@Override
public void handleRequest( HttpServerExchange exchange ) {
exchange.responseBody( "hello" );
}
}The endpoint is available at GET http://localhost:8080/api/hello.
See oap-http for the full server reference.
| Need | Add module |
|---|---|
| High-performance non-blocking pipeline | oap-pnio-v3 |
| Prometheus metrics scrape endpoint | oap-http-prometheus |
| HTTP assertions in tests | oap-http-test |
HTTP web service framework for the OAP platform. Provides annotation-driven endpoint declaration, session management, interceptors, validation, OpenAPI generation, SSO/JWT security, and file upload/download — all wired through the OAP Kernel with zero servlet-container boilerplate.
| Module | Description | Depends on |
|---|---|---|
| oap-ws | Core framework: @WsMethod, @WsParam, WebServices, SessionManager, validation |
oap-http |
| oap-ws-admin-ws | Built-in admin endpoints: log level control, JPath queries, JSON schema lookup | oap-ws |
| oap-ws-api-api | Shared API descriptor contracts (Info, @OpenapiIgnore) |
oap-ws |
| oap-ws-api-ws | HTTP endpoint that exposes the service registry as JSON (GET /system/api) |
oap-ws |
| oap-ws-file-ws | File upload and download over HTTP with multi-bucket storage | oap-ws |
| oap-ws-openapi | Core OpenAPI 3.x generation library (OpenapiGenerator, WebServicesWalker) |
oap-ws |
| oap-ws-openapi-ws | HTTP endpoint that serves the generated OpenAPI spec (GET /system/openapi) |
oap-ws, oap-ws-api-ws |
| oap-ws-openapi-maven-plugin | Maven plugin to generate swagger.json / YAML at build time |
— |
| oap-ws-sso-api | SSO contracts + interceptors: @WsSecurity, JWT, API key, throttle-login |
— |
| oap-ws-sso | AbstractSecureWS base class for secured web services |
oap-ws-sso-api |
| oap-ws-test | TestNG assertion helpers for validation errors | oap-ws |
1. Add oap-ws to your module's dependsOn:
name = my-module
dependsOn = [oap-ws]2. Annotate your service class and register it with a ws-service block:
public class HelloWS {
@WsMethod( path = "/hello", method = HttpMethod.GET )
public String hello( @WsParam( from = From.QUERY ) String name ) {
return "Hello, " + name + "!";
}
}services {
hello-ws {
implementation = com.example.HelloWS
ws-service.path = api
}
}3. The endpoint is available at GET /api/hello?name=World.
See the oap-ws module for the full reference.
| Need | Add module |
|---|---|
| Runtime API introspection | oap-ws-api-ws |
| OpenAPI / Swagger spec served at runtime | oap-ws-openapi-ws |
| OpenAPI spec generated at build time | oap-ws-openapi-maven-plugin |
| JWT / API-key authentication (query params or headers) | oap-ws-sso-api |
| File upload / download | oap-ws-file-ws |
| Admin (log levels, JPath) | oap-ws-admin-ws |
JPath expression language for navigating Java objects and maps using reflection. Expressions are parsed by an ANTLR4 grammar and evaluated against a variable map, traversing public and private fields, calling methods, and indexing arrays or lists — all in a single ${…} expression.
Every JPath expression is wrapped in ${…}. The first segment names a variable from the provided map; subsequent segments are chained with ..
${variable}
${variable.field}
${variable.field.nestedField}
${variable.method()}
${variable.method("arg", 2)}
${variable.array[0]}
${variable.list[1].field}
| Form | Example | Resolves via |
|---|---|---|
identifier |
name |
Field access — public or private, via reflection |
name(args…) |
getLabel("x", 2) |
Method call — public or private, via reflection |
name[n] |
items[1] |
Array element or List.get(n) |
Segments can be chained freely:
${order.lines[0].product.getPrice("USD")}
Methods accept string literals and decimal integer literals as arguments.
| Literal | Example | Parsed as |
|---|---|---|
| String | "hello" |
String |
| Decimal integer | 42 |
Parsed as Long, auto-coerced to the target parameter type (int, long, float, double, short, byte) |
StringBuilderJPathOutput output = new StringBuilderJPathOutput();
JPath.evaluate(
"${user.address.city}",
Map.of( "user", user ),
output
);
String result = output.toString();Static shorthand — builds a JPath instance and evaluates in one call. For repeated evaluation against the same variable set, construct a JPath instance directly:
JPath jpath = new JPath( Map.of( "user", user ) );
jpath.evaluate( "${user.name}", output );
output.reset();
jpath.evaluate( "${user.email}", output );Built-in JPathOutput implementation that collects results into a StringBuilder.
| Method | Description |
|---|---|
toString() |
Returns the accumulated string value |
reset() |
Clears the buffer for re-use |
// Simple variable lookup
JPath.evaluate( "${id}", Map.of( "id", 42 ), output );
// → "42"
// Nested field access (public field)
JPath.evaluate( "${order.status}", Map.of( "order", order ), output );
// Private field access
JPath.evaluate( "${bean.internalState}", Map.of( "bean", bean ), output );
// Private method call
JPath.evaluate( "${bean.computeScore()}", Map.of( "bean", bean ), output );
// Method with string argument
JPath.evaluate( "${bean.format(\"prefix\")}", Map.of( "bean", bean ), output );
// Method with multiple arguments (string + integer)
JPath.evaluate( "${bean.pad(\"x\", 5)}", Map.of( "bean", bean ), output );
// Array element access
JPath.evaluate( "${data.scores[2]}", Map.of( "data", data ), output );
// List element + field chain
JPath.evaluate( "${order.lines[0].productName}", Map.of( "order", order ), output );
// Chaining Java API calls
JPath.evaluate( "${map.keySet().stream().count()}", Map.of( "map", map ), output );JPathOutput is a @FunctionalInterface. Implement it to collect typed values without converting to a string:
List<Object> collected = new ArrayList<>();
JPathOutput collector = pointer -> collected.add( pointer.get() );
JPath.evaluate( "${item.price}", Map.of( "item", item ), collector );
BigDecimal price = (BigDecimal) collected.get( 0 );The Pointer passed to write is one of:
| Implementation | get() returns |
|---|---|
ObjectPointer<T> |
The resolved object |
MapPointer |
The resolved Map |
NullPointer |
null |
| Exception | Thrown when |
|---|---|
PathNotFoundException |
A field or method named in the expression does not exist on the target object |
ReflectionException |
Reflection access fails (e.g., module access denied) |
JPathWS— exposes JPath evaluation over the live Kernel service tree as a JSON HTTP endpoint.InspectorWS— browsable HTML UI built on top of the same JPath queries.
Format processing modules for the OAP platform: template engine, TSV/CSV, JSON schema validation, and log streaming.
| Module | Description |
|---|---|
| oap-template | Compile-time template engine — parses once, compiles to Java, renders at near-native speed |
| oap-template-test | TemplateEngineFixture — TestNG fixture for template engine tests |
| oap-json | JSON schema validation (HOCON format) and structural diff |
| oap-tsv | TSV/CSV parsing, streaming, and printing |
| oap-logstream | High-throughput transactional log streaming to time-bucketed gzip files |
Distributed, in-memory statistics database for the OAP platform. Data is organized as a typed key hierarchy — each level of the tree holds a Node.Value that knows how to merge itself with another value of the same type. Parent nodes optionally aggregate over their children after each update.
StatsDBNode (process A) StatsDBNode (process B)
update("k1","k2", v -> v.n++) update("k1","k3", v -> v.n++)
sync() ─────────────────────┐ sync() ────────────────────┐
▼ ▼
StatsDBMaster (in-memory tree)
k1 → MockChild (aggregate)
k2 → MockValue
k3 → MockValue
▼ (periodically)
StatsDBStorage (MongoDB / NULL)
In a single-process deployment, use StatsDBMaster directly without a StatsDBNode.
| Module | Description | Depends on |
|---|---|---|
| oap-statsdb-common | Core: Node.Value, Node.Container, NodeSchema, StatsDB API |
— |
| oap-statsdb-master | StatsDBMaster, StatsDBStorage, MongoDB persistence, message listener |
oap-statsdb-common |
| oap-statsdb-node | StatsDBNode, StatsDBTransport, message-based sync transport |
oap-statsdb-common |
| oap-statsdb-test | StatsDBTransportMock for integration tests |
oap-statsdb-master, oap-statsdb-node |
The value stored at each tree node. Must implement merge(T other) — called when a sync from a remote node arrives — and Serializable.
public class Counters implements Node.Value<Counters> {
public long requests;
public long errors;
@Override
public Counters merge( Counters other ) {
requests += other.requests;
errors += other.errors;
return this;
}
}A value at an intermediate tree level that rolls up metrics from its children. aggregate(List<TChild>) is called automatically after every update on any descendant.
public class RollupCounters implements Node.Container<RollupCounters, Counters> {
public long totalRequests;
@Override
public RollupCounters merge( RollupCounters other ) {
// merge is additive — called when syncing from remote nodes
return this;
}
@Override
public RollupCounters aggregate( List<Counters> children ) {
totalRequests = children.stream().mapToLong( c -> c.requests ).sum();
return this;
}
}Mark computed fields @JsonIgnore if they should not be persisted (they are re-derived from children on load).
Declares the Node.Value class at each key level, ordered from root to leaf.
NodeSchema schema = new NodeSchema(
nc( "endpoint", RollupCounters.class ), // level 0 — root
nc( "date", Counters.class ) // level 1 — leaf
);nc(String key, Class<T>) is a static factory on NodeSchema.
Register value classes in oap-module.oap so the JSON binder can deserialize them:
configurations = [
{
loader = oap.json.TypeIdFactory
config {
counters = com.example.Counters
rollup-counters = com.example.RollupCounters
}
}
]All update and query methods are available on both StatsDBMaster and StatsDBNode.
// 1-key update (leaf at level 0)
db.<Counters>update( "endpoint-a", v -> v.requests++ );
// 2-key update (leaf at level 1)
db.<Counters>update( "endpoint-a", "2024-06-01", v -> {
v.requests++;
v.errors++;
} );
// Up to 5 keys supported
db.<Counters>update( k1, k2, k3, k4, k5, v -> v.requests++ );// Get value at a path (returns null if not present)
Counters c = db.get( "endpoint-a", "2024-06-01" );
// Get all child values under a prefix
Stream<Counters> daily = db.children( "endpoint-a" );Use select2() … select5() to stream over the full tree with typed key-value tuples:
// 2-level tree: (id1, v1) → (id2, v2)
db.<RollupCounters, Counters>select2().forEach( row -> {
System.out.println( row.id1 + " " + row.id2 + " requests=" + row.v2.requests );
} );
// 3-level tree
db.<T1, T2, T3>select3().forEach( row -> { … } );
// also select4(), select5()| Method | Fields |
|---|---|
select2() |
id1, v1, id2, v2 |
select3() |
id1, v1, id2, v2, id3, v3 |
select4() |
id1, v1, id2, v2, id3, v3, id4, v4 |
select5() |
id1, v1, id2, v2, id3, v3, id4, v4, id5, v5 |
db.removeAll(); // clears in-memory state onlyNodeSchema schema = new NodeSchema(
nc( "endpoint", RollupCounters.class ),
nc( "date", Counters.class )
);
try( StatsDBMaster master = new StatsDBMaster( schema, StatsDBStorage.NULL ) ) {
master.<Counters>update( "search", "2024-06-01", v -> v.requests += 5 );
master.<Counters>update( "search", "2024-06-02", v -> v.requests += 3 );
// Roll-up is automatic
assertThat( master.<RollupCounters>get( "search" ).totalRequests ).isEqualTo( 8 );
}Reliable, durable HTTP message delivery for the OAP platform. The sender buffers messages to disk when the network is unavailable and retries until acknowledged. The server deduplicates by MD5 so retries are always safe to replay.
MessageSender (client process)
send(type, data)
│
├─ in-memory queue ──► syncMemory() ──► POST /messages ──► MessageHttpHandler
│ │
└─ disk (on shutdown/failure) ├─ MD5 dedup (MessageHashStorage)
syncDisk() reloads on restart │
└─ MessageListener.run(...)
→ short status
| Field | Type | Description |
|---|---|---|
| message type | byte |
User-defined type identifier (0–200) |
| version | short |
Message schema version |
| client ID | long |
Unique sender ID (per MessageSender instance) |
| MD5 | byte[16] |
MD5 digest of the payload |
| reserved | byte[8] |
Reserved, always zero |
| data size | int |
Payload length in bytes |
| payload | byte[N] |
Message body |
| Field | Type | Description |
|---|---|---|
| protocol version | byte |
Always 1 |
| client ID | long |
Echoed from request |
| MD5 | byte[16] |
Echoed from request |
| reserved | byte[8] |
Reserved |
| status | short |
See status codes below |
| Constant | Value | Meaning |
|---|---|---|
STATUS_OK |
0 |
Processed successfully |
STATUS_UNKNOWN_ERROR |
1 |
Processing failed — client will retry |
STATUS_UNKNOWN_ERROR_NO_RETRY |
2 |
Processing failed — client drops the message |
STATUS_UNKNOWN_MESSAGE_TYPE |
100 |
No listener registered for this type — client drops the message |
STATUS_ALREADY_WRITTEN |
101 |
Duplicate — server already processed this MD5; treated as success by the client |
Custom status codes (causing retry) can be registered in META-INF/oap-messages.properties using the map.* prefix — see oap-message-server.
| Module | Description | Depends on |
|---|---|---|
| oap-message-client | MessageSender — durable send queue with disk persistence |
oap-http |
| oap-message-server | MessageHttpHandler, MessageListener, MessageListenerJson |
oap-http |
| oap-message-test | MessageListenerMock, MessageListenerJsonMock, MessageSenderUtils |
oap-message-client, oap-message-server |
Persistent, in-memory storage layer for the OAP platform. Objects are kept in a ConcurrentHashMap-backed MemoryStorage and optionally synced to MongoDB or cloud object stores.
┌─────────────────────────────┐
│ MemoryStorage<Id,Data> │
│ (ConcurrentHashMap + Lock) │
└──────────┬──────────────────┘
│ TransactionLog (change log)
┌──────────────┴──────────────────┐
│ │
MongoPersistence<I,T> ReplicationMaster / RemoteStorage
(periodic bulk write, (diff-based replication
change stream watch) between nodes)
Cloud storage (FileSystem) is a separate, stateless API over object stores — it does not integrate with MemoryStorage.
| Module | Description | Depends on |
|---|---|---|
| oap-storage | Storage<Id,Data>, MemoryStorage, Metadata, DataListener, Migration |
oap-stdlib |
| oap-storage-mongo | MongoPersistence, MongoClient, MongoIndex, Version |
oap-storage |
| oap-storage-cloud | FileSystem, CloudURI, FileSystemConfiguration, FileSystemCloudApi |
oap-stdlib |
| oap-storage-cloud-aws-s3 | AWS S3 backend (s3:// scheme) |
oap-storage-cloud |
| oap-storage-mongo-test | MongoFixture — in-memory MongoDB for tests |
oap-storage-mongo |
| oap-storage-cloud-test | S3MockFixture — LocalStack S3 for tests |
oap-storage-cloud-aws-s3 |
// Define an identifier — extracts/assigns the String key from your object
Identifier<String, MyData> id = Identifier.forId( d -> d.id, ( d, newId ) -> d.id = newId )
.suggestion( d -> d.name )
.build();
// In-memory store, concurrent reads and writes
MemoryStorage<String, MyData> storage = new MemoryStorage<>( id, Lock.CONCURRENT );
// Store
storage.store( new MyData( "item-1", "hello" ), "system" );
// Read
Optional<MyData> found = storage.getNullable( "item-1" );
// Update in place
storage.update( "item-1", d -> { d.name = "world"; return d; } );
// Listen to changes
storage.addDataListener( new Storage.DataListener<String, MyData>() {
@Override
public void updated( IdObject<String, MyData> previous, IdObject<String, MyData> updated ) {
System.out.println( "changed: " + updated.id );
}
} );CPU affinity utility for the OAP platform. Pins the calling thread to a specific CPU core via net.openhft.affinity, reducing cross-core cache misses in high-throughput loops (network I/O, encoding, scheduling).
A plain utility class — not a managed OAP service. Instantiate it directly wherever you need to control thread placement.
The constructor accepts a string that describes which CPU cores to use:
| Expression | Meaning | Example → CPUs |
|---|---|---|
* |
No affinity (disabled) | * → [] |
n |
Single core | 3 → [3] |
n-m |
Inclusive range | 1-3 → [1, 2, 3] |
n+ |
Core n through the last available |
4+ on 8-core → [4, 5, 6, 7] |
| Comma-separated | Combine any of the above | 1-3, 8 → [1, 2, 3, 8] |
Affinity affinity = new Affinity( "2-5" ); // cores 2, 3, 4, 5
Affinity affinity = new Affinity( "0+" ); // all cores from 0 upward
Affinity affinity = new Affinity( "*" ); // disabled — no pinning
Affinity affinity = Affinity.any(); // same as "*"| Method | Description |
|---|---|
set() |
Pin the calling thread to the next core in the set (round-robin); no-op when disabled |
isEnabled() |
false when constructed with *; true otherwise |
size() |
Number of CPU cores in the configured set |
getCpus() |
Raw int[] of configured core indices |
Call set() once per thread at startup, or at the top of a processing loop when you want round-robin distribution across the configured cores:
Affinity affinity = new Affinity( "4+" ); // dedicate upper cores to this pool
ExecutorService pool = Executors.newFixedThreadPool( affinity.size(), r -> {
Thread t = new Thread( () -> {
affinity.set(); // pin this thread before doing any work
r.run();
} );
return t;
} );When isEnabled() is false (e.g. * in config), set() is a no-op and the JVM scheduler assigns cores freely — no code path changes needed.
Email delivery for the OAP platform. Provides a persistent delivery queue, Velocity-based message templates, and swappable transports (SMTP, SendGrid).
Template → Message → Mailman → MailQueue → Transport
↕
MailQueuePersistence
(file / memory / MongoDB)
Mailman runs as a supervised background thread that drains MailQueue. Failed messages are retried on a configurable schedule; messages that remain broken past brokenMessageTTL are dropped.
| Module | Description |
|---|---|
| oap-mail | Core: Message, Mailman, MailQueue, SmtpTransport, Template |
| oap-mail-sendgrid | SendGrid REST API transport |
| oap-mail-mongo | MongoDB-backed queue persistence |
| oap-mail-test | TransportMock, MessageAssertion, MessagesAssertion, MailBox |
Build-time code generation and packaging utilities for OAP projects. All goals share the prefix oap.
| Goal | Module | Phase | Description |
|---|---|---|---|
oap:generate |
oap-dictionary-maven |
generate-sources |
Generate Java enums from dictionary JSON/HOCON files |
oap:startup-scripts |
oap-application-maven |
prepare-package |
Generate OS service scripts (systemd, sysvinit, shell) |
oap:copy |
oap-maven |
prepare-package |
Copy file sets into a directory with optional property filtering |
