docs(comments): fill the real comment gaps across the repository

A comment-health audit driven by an AST scanner (regex scanners kept
false-positiving on annotation-separated javadoc; the AST pass with
constructor and @Override-implementation exemptions is what produced a
trustworthy worklist) ended with a much smaller true gap than the raw
numbers suggested: class-level javadoc is already at 100% repository-wide.

What actually gets filled here:

- AnalyticsModel: all 30 nested records of the S19 analytics facet get
  maintainer-voice javadoc (the @Schema descriptions serve the API side;
  these serve code readers).
- The whole entity/ext family (29 files, 47 nested classes): every
  Content/Ui/Validation/Security/AppliesTo/Template/Dedup/... schema
  class states what it actually holds — read from the fields, not
  templated.
- Adapter families: the @Bean methods of all six MQ adapters, all four
  TSDB adapters and the three DB dialect adapters get one-line
  maintainer docs (what binds to what, what is overridable).
- Nested support types across data/agentic/constant/public/facade:
  NotifyBindKey, CacheTuning, Credential, dashboard VOs' Item/BucketVO,
  ExpireListener (plus its null-doc placeholder javadoc replaced with a
  real contract), agentic chat/stream VOs' choice/delta/usage shapes,
  tool-context records, RequestHeader envelopes, FleetSummary,
  visualization Type/Scale codes.
- PointValueSampleConverter's boundary methods now state the port-side
  contract (quality default injection point, batch order preservation).

Comment drift fixed: the class javadoc of the can/mqtt/opc-da driver
skeletons claimed "see TODO markers in method bodies" — those markers
do not exist; the claims are corrected to plain work-in-progress notes.
The genuine TODOs (zigbee API verification, lwm2m lookup, ethernet-ip
CIP framing, CAN JNI) stay: they mark real unimplemented behavior.

Vendored-code boundary: the opc-da module carries a ported
org.openscada library (46 files, package org.openscada.*). Its trivial
accessors stay bare on purpose — filler comments on get/set pairs are
exactly the kind of noise this pass exists to remove — but the
non-obvious JIStruct wire-decoder methods (getStruct/fromStruct
FILETIME + three DO wrappers) now document the COM bridge shape.

Gates: full-repo compile green; data/model/constant/agentic/public
test suites green.
This commit is contained in:
pnoker
2026-08-24 21:23:39 +08:00
parent 4371650a6e
commit 1fa2d6f0cf
107 changed files with 519 additions and 65 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ types use `io.github.pnoker.api.center.data`; proto sources live under `src/main
| Service | RPCs | Purpose |
|---|---|---|
| `PointValueApi` | `GetLastValue`, `ListHistoryValues` | query point values |
| `PointValueApi` | `GetLastValue`, `ListHistoryValues`, `ListSeriesVolumes` | query point values and value-volume series |
| `PointValueApi` | `ReadCommand`, `WriteCommand` | submit point read/write commands |
| `CommandHistoryApi` | `CallCommand`, `GetByRecordId`, `ListByPage` | dispatch and query command history |
| `EventHistoryApi` | `ReportEvent`, `GetByRecordId`, `ListByPage` | report and query event history |
+4 -4
View File
@@ -2,9 +2,9 @@
## Overview
`dc3-center-data` is the Data Center of the IoT DC3 platform. It integrates common messaging middleware including AMQP,
WebSocket, and MQTT for collecting device point values from drivers, storing them in the time-series repository, and
exposing data query APIs.
`dc3-center-data` is the Data Center of the IoT DC3 platform. It consumes device point values from drivers over
RabbitMQ (AMQP), stores them through the pluggable time-series port (`dc3-tsdb`, TimescaleDB by default), and exposes
data query and command APIs.
## Module Information
@@ -96,4 +96,4 @@ mvn -s .mvn/settings.xml -pl dc3-center/dc3-center-data -am test
- `dc3-api-data` - gRPC API contracts for point value queries
- `dc3-api-manager` - gRPC API for resolving driver/point metadata
- `dc3-common-data` - Business logic implementation
- `dc3-common-repository` - Pluggable time-series storage adapter
- `dc3-tsdb-core` - Pluggable time-series storage adapter (TimescaleDB adapter selected by default)
+6 -1
View File
@@ -27,7 +27,7 @@ management, and command interfaces.
- **Point Management**: Point definitions, type flags, scale/precision settings
- **gRPC Server**: Implements `DriverApi`, `DeviceApi`, `PointApi` for driver registration and data query
- **Metadata Events**: Publishes metadata change events over RabbitMQ to notify relevant drivers
- **Scheduled Jobs**: Hourly jobs for platform statistics (e.g., point data volume)
- **Scheduled Jobs**: Quartz-based hourly maintenance job (`HourlyJobForManager`)
## REST Endpoints (via Gateway)
@@ -43,9 +43,14 @@ Key endpoint prefixes (defined in `ManagerConstant`):
| `/point` | Point definitions |
| `/driver_attribute` | Driver-level attributes |
| `/point_attribute` | Point-level attributes |
| `/command` | Device commands |
| `/event` | Device events |
| `/group` | Device groups |
| `/topic` | MQTT/data topics |
The complete prefix set (labels, dictionaries, attribute configs, dashboards, batch operations) lives in
`ManagerConstant`.
## gRPC Services (consumed by drivers and data service)
| Service | Used by |
+7 -3
View File
@@ -30,9 +30,6 @@ center and driver applications.
| Module | Responsibility |
|---|---|
| `dc3-common-dal` | shared label/group persistence |
| `dc3-common-postgres` | datasource and MyBatis-Plus configuration |
| `dc3-common-repository` | point-value storage abstraction |
| `dc3-common-rabbitmq` | shared exchanges, connection configuration, and message conversion |
| `dc3-common-mqtt` | MQTT client configuration |
| `dc3-common-quartz` | scheduling infrastructure |
| `dc3-common-thread` | managed executors |
@@ -42,6 +39,13 @@ center and driver applications.
| `dc3-common-resource-registrar` | API/resource annotation discovery and synchronization |
| `dc3-common-test` | shared tests, harnesses, and Testcontainers |
Datasource, messaging, and time-series storage were split into dedicated top-level families; business modules depend on
them directly:
- `dc3-db` — relational dialect adapters (PostgreSQL/MySQL/MariaDB) behind `dc3.db.type`
- `dc3-mq` — broker-neutral messaging port with per-broker adapters (RabbitMQ, Kafka, RocketMQ, Pulsar, ActiveMQ, MQTT)
- `dc3-tsdb` — store-neutral time-series port with per-store adapters (TimescaleDB, TDengine, InfluxDB, IoTDB)
## Architecture rules
- Preserve `Controller -> Service -> Manager -> Mapper` layering.
+1 -1
View File
@@ -31,7 +31,7 @@ model uses to read and operate platform resources.
## Dependencies
- `spring-ai-starter-model-chat-memory-repository-jdbc` — Spring AI chat client + JDBC memory
- `spring-ai-starter-model-chat-memory-repository-jdbc` — Spring AI JDBC-backed conversation memory
## Build Instructions
@@ -74,6 +74,7 @@ public class AgenticMessageContent implements Serializable {
return content;
}
/** Tool-call trace entry (type, title, detail) for the run timeline. */
@Getter
@Setter
@ToString
@@ -142,6 +143,7 @@ public class AgenticMessageContent implements Serializable {
}
/** One context block (type + content) fed to the model. */
@Getter
@Setter
@ToString
@@ -171,6 +173,7 @@ public class AgenticMessageContent implements Serializable {
}
/** Token accounting: input/output/text/context counts of one message. */
@Getter
@Setter
@ToString
@@ -62,6 +62,7 @@ public class AgenticVisualizationSpec implements Serializable {
private List<Annotation> annotations;
/** Visual-channel bindings: which field maps to x/y/color/size. */
@Getter
@Setter
@ToString
@@ -98,6 +99,7 @@ public class AgenticVisualizationSpec implements Serializable {
}
/** One chart annotation (type, value, label). */
@Getter
@Setter
@ToString
@@ -60,6 +60,7 @@ public class ChatCompletionChunkVO {
@Schema(description = "List of streaming choices included in this chunk; typically contains exactly one element for non-branching completions.")
private List<ChunkChoice> choices;
/** One streaming choice: index + incremental delta. */
@Getter
@Setter
@NoArgsConstructor
@@ -82,6 +83,7 @@ public class ChatCompletionChunkVO {
}
/** Incremental content/role fragment of one chunk. */
@Getter
@Setter
@NoArgsConstructor
@@ -64,6 +64,7 @@ public class ChatCompletionResponseVO {
@Schema(description = "Token usage statistics for the request and response.")
private Usage usage;
/** One completion choice: index + assistant message. */
@Getter
@Setter
@NoArgsConstructor
@@ -85,6 +86,7 @@ public class ChatCompletionResponseVO {
}
/** Generated assistant message (role + content). */
@Getter
@Setter
@NoArgsConstructor
@@ -107,6 +109,7 @@ public class ChatCompletionResponseVO {
}
/** Token usage of the completion (prompt/completion/total). */
@Getter
@Setter
@NoArgsConstructor
@@ -48,6 +48,7 @@ public class TenantTool {
return AgenticToolResult.ok("Current tenant context loaded", new CurrentTenantContext(tenantId));
}
/** The tenant injected from the authenticated ToolContext (never agent-supplied). */
public record CurrentTenantContext(Long tenantId) {
}
@@ -51,6 +51,7 @@ public class UserTool {
new CurrentUserProfile(userId, header.getUserName(), header.getNickName()));
}
/** The acting user resolved from the ToolContext identity. */
public record CurrentUserProfile(Long userId, String username, String nickname) {
}
+1
View File
@@ -14,6 +14,7 @@ builder utilities for constructing gRPC request/response objects from domain mod
- **`GrpcBuilderUtil`** — Utility class for building common gRPC DTOs from BO/DO entities (e.g., setting pagination,
building result wrappers)
- **`GrpcRFactory`** — Builds `GrpcR` response envelopes
## Dependencies
+2 -1
View File
@@ -58,7 +58,8 @@ consumer, and deployed queue migration.
### Enumerations
Located in `io.github.pnoker.common.enums`:
Located in `io.github.pnoker.common.enums`. The list below is a selection of the most commonly referenced enums; see
the package for the complete set (alarm, MCP, OAuth, notify, and other domain enums live there too):
- `EnableFlagEnum` — Boolean-like enable/disable state
- `EntityStatusEnum` — Online, offline, maintenance, and fault states
@@ -147,6 +147,7 @@ public class AgenticConstant {
throw new IllegalStateException(BaseConstant.UTILITY_CLASS);
}
/** Chart type codes the visualization channel accepts (line/area/column/bar/pie/donut/heatmap/scatter/stat). */
public static class Type {
public static final String LINE = "line";
@@ -173,6 +174,7 @@ public class AgenticConstant {
}
/** Axis scale kinds: linear or time. */
public static class Scale {
public static final String LINEAR = "linear";
+1 -1
View File
@@ -33,6 +33,6 @@ mvn -s .mvn/settings.xml -q -pl dc3-common/dc3-common-dal -am -DskipTests compil
## Related Modules
- `dc3-common-postgres`PostgreSQL and MyBatis-Plus base configuration
- `dc3-db-core` / `dc3-db-postgres`datasource and MyBatis-Plus base configuration
- `dc3-common-model` — Base BO/VO/DTO model definitions
- `dc3-common-manager` / `dc3-common-auth` / `dc3-common-data` — Consumers of this DAL layer
+2 -2
View File
@@ -98,5 +98,5 @@ mvn -s .mvn/settings.xml -pl dc3-common/dc3-common-data -am test
- `dc3-center-data` — Bootstraps this module as a Spring Boot service
- `dc3-api-manager` — gRPC API consumed by this module for driver/point resolution
- `dc3-common-repository`Storage adapter for persisting point values
- `dc3-common-rabbitmq` — RabbitMQ exchange/queue configuration
- `dc3-tsdb-core` / `dc3-tsdb-timescale`store-neutral time-series port and TimescaleDB adapter
- `dc3-mq-core` / `dc3-mq-rabbitmq` — broker-neutral messaging port and RabbitMQ adapter
@@ -243,6 +243,7 @@ public class NotifyConfigCache {
bindCache.invalidateAll();
}
/** Cache key of one notify rule's channel bindings: tenant + notify id. */
public record NotifyBindKey(Long tenantId, Long notifyId) {
}
@@ -52,6 +52,10 @@ public class PointValueSampleConverter {
return Objects.isNull(instant) ? null : LocalDateTime.ofInstant(instant, TimeConstant.DEFAULT_ZONEID);
}
/**
* Business BO → port sample. Quality defaults to 0 (GOOD) — the business
* layer does not model quality yet; when it does, this is the injection point.
*/
public PointValueSample toSample(PointValueBO valueBO) {
return new PointValueSample(
new SeriesKey(valueBO.getTenantId(), valueBO.getDeviceId(), valueBO.getPointId()),
@@ -64,6 +68,7 @@ public class PointValueSampleConverter {
valueBO.getFencingToken(), valueBO.getDriverId());
}
/** Port sample → business BO; timestamps return to wall-clock in the platform zone. */
public PointValueBO toBO(PointValueSample sample) {
return PointValueBO.builder()
.tenantId(sample.series().tenantId())
@@ -83,10 +88,12 @@ public class PointValueSampleConverter {
.build();
}
/** Batch form of {@link #toSample(PointValueBO)}, order-preserving. */
public List<PointValueSample> toSamples(List<PointValueBO> values) {
return values.stream().map(this::toSample).toList();
}
/** Batch form of {@link #toBO(PointValueSample)}, order-preserving. */
public List<PointValueBO> toBOs(List<PointValueSample> samples) {
return samples.stream().map(this::toBO).toList();
}
@@ -176,14 +176,16 @@ public class LocalCacheImpl {
expireListeners.add(listener);
}
/** Callback fired (best-effort, on the eviction path) when a TTL-bound entry expires. */
@FunctionalInterface
public interface ExpireListener {
/**
* On expire.
* React to one expired entry. Exceptions here are logged and swallowed —
* an eviction listener must never break the cache itself.
*
* @param key key
* @param lastValue last value
* @param key the expired cache key
* @param lastValue the entry's value as of eviction
*/
void onExpire(String key, Object lastValue);
@@ -47,6 +47,11 @@ public class AlarmCacheProperties {
@Valid
private CacheTuning notify = new CacheTuning(5_000L, 60L);
/**
* Per-cache sizing: Caffeine maximum entries plus entry TTL in seconds. The
* defaults favor correctness over memory (short TTLs) — rule/notify lookups
* are cheap to rebuild from the database.
*/
@Getter
@Setter
public static class CacheTuning {
@@ -57,10 +62,11 @@ public class AlarmCacheProperties {
@Min(value = 1, message = "Cache ttl-seconds must be at least 1")
private long ttlSeconds;
/** Required by Spring property binding. */
public CacheTuning() {
// Spring property binding requires a no-arg constructor.
}
/** All-args constructor for programmatic tuning. */
public CacheTuning(long maxSize, long ttlSeconds) {
this.maxSize = maxSize;
this.ttlSeconds = ttlSeconds;
@@ -45,6 +45,7 @@ public class NotifyCredentialProperties {
*/
private Map<String, Credential> credentials = new LinkedHashMap<>();
/** One channel-type credential bundle (e.g. Feishu bot webhook URL), keyed by type. */
@Getter
@Setter
public static class Credential {
@@ -43,6 +43,10 @@ public final class AnalyticsModel {
* One series addressed either by ids or by names — ids win when both are
* present; ambiguous or unknown names surface the candidates in the error.
*/
/**
* One series addressed either by ids or by names — ids win when both are present;
* ambiguous or unknown names surface the candidates in the error.
*/
public record SeriesSelector(
@Schema(description = "Device id; takes precedence over deviceName when both are given", example = "1024") Long deviceId,
@Schema(description = "Point id; takes precedence over pointName when both are given", example = "2048") Long pointId,
@@ -54,6 +58,10 @@ public final class AnalyticsModel {
* Window as an ISO-8601 instant pair, or {@code rangeHours} back from now
* when the pair is absent. An empty range defaults to the last 24 hours.
*/
/**
* Window as an ISO-8601 instant pair, or {@code rangeHours} back from now when the pair is
* absent. An empty range defaults to the last 24 hours.
*/
public record TimeRange(
@Schema(description = "Inclusive window start, ISO-8601 instant", example = "2026-08-20T00:00:00Z") String fromIso,
@Schema(description = "Exclusive window end, ISO-8601 instant; defaults to now", example = "2026-08-21T00:00:00Z") String toIso,
@@ -62,10 +70,17 @@ public final class AnalyticsModel {
// ===== requests =====
/**
* Body of {@code POST /analytics/query_latest}: current values of one or more series.
*/
public record QueryLatestRequest(
@Schema(description = "Series to read; at most 20 per call") List<SeriesSelector> series) {
}
/**
* Body of {@code POST /analytics/query_history}: RAW mode returns the newest samples (bounded);
* M4 mode returns per-bucket first/min/max/last for chart-grade rendering.
*/
public record QueryHistoryRequest(
@Schema(description = "Series to read; at most 20 per call") List<SeriesSelector> series,
@Schema(description = "Time window; defaults to the last 24 hours") TimeRange window,
@@ -73,18 +88,29 @@ public final class AnalyticsModel {
@Schema(description = "Target point count per series (1..1000); RAW caps the total pull, M4 sizes the buckets", example = "200") Integer maxPoints) {
}
/**
* Body of {@code POST /analytics/compute_stats}: statistical profile request with optional
* percentiles (defaults to 0.5 and 0.95).
*/
public record ComputeStatsRequest(
@Schema(description = "Series to profile; at most 20 per call") List<SeriesSelector> series,
@Schema(description = "Time window; defaults to the last 24 hours") TimeRange window,
@Schema(description = "Percentiles in [0,1] to compute per series; defaults to 0.5 and 0.95", example = "[0.5, 0.99]") List<Double> percentiles) {
}
/**
* Body of {@code POST /analytics/compare_periods}: same series across two windows.
*/
public record ComparePeriodsRequest(
@Schema(description = "Series to compare; at most 20 per call") List<SeriesSelector> series,
@Schema(description = "Current window (e.g. this week)") TimeRange current,
@Schema(description = "Baseline window (e.g. last week)") TimeRange baseline) {
}
/**
* Body of {@code POST /analytics/rank_entities}: ranking by ACTIVITY (sample count) or by
* MEAN / MAX / MIN over each entity's series.
*/
public record RankEntitiesRequest(
@Schema(description = "Grouping dimension of the ranking", example = "DEVICE", allowableValues = {"DEVICE", "POINT", "DRIVER"}) String dimension,
@Schema(description = "Ranking metric: ACTIVITY (sample count) or MEAN / MAX / MIN over each entity's series", example = "ACTIVITY", allowableValues = {"ACTIVITY", "MEAN", "MAX", "MIN"}) String metric,
@@ -92,12 +118,20 @@ public final class AnalyticsModel {
@Schema(description = "Top-N size (1..50); defaults to 10", example = "10") Integer limit) {
}
/**
* Body of {@code POST /analytics/trend_analysis}: per-series least-squares trend over bucket
* averages; {@code buckets} clamped to [2, 200].
*/
public record TrendAnalysisRequest(
@Schema(description = "Series to analyze; at most 20 per call") List<SeriesSelector> series,
@Schema(description = "Time window; defaults to the last 24 hours") TimeRange window,
@Schema(description = "Bucket count for the regression (2..200); defaults to 50", example = "50") Integer buckets) {
}
/**
* Body of {@code POST /analytics/threshold_report}: exceedance report against a threshold
* with a GREATER / LESS operator.
*/
public record ThresholdReportRequest(
@Schema(description = "Series to report; at most 20 per call") List<SeriesSelector> series,
@Schema(description = "Time window; defaults to the last 24 hours") TimeRange window,
@@ -105,6 +139,10 @@ public final class AnalyticsModel {
@Schema(description = "Threshold value samples are compared against", example = "80.0") Double threshold) {
}
/**
* Body of {@code POST /analytics/correlate}: Pearson correlation between two series over
* aligned buckets (default 300 s).
*/
public record CorrelateRequest(
@Schema(description = "First series of the pair") SeriesSelector seriesA,
@Schema(description = "Second series of the pair") SeriesSelector seriesB,
@@ -112,6 +150,10 @@ public final class AnalyticsModel {
@Schema(description = "Bucket length in seconds for alignment (10..86400); defaults to 300", example = "300") Long alignBucketSeconds) {
}
/**
* Body of {@code POST /analytics/data_quality_report}: tenant-level coverage, silence and
* quality-code census.
*/
public record QualityReportRequest(
@Schema(description = "Time window; defaults to the last 24 hours") TimeRange window,
@Schema(description = "Minutes of inactivity after which a series counts as silent (5..1440); defaults to 30", example = "30") Integer silentMinutes) {
@@ -119,6 +161,9 @@ public final class AnalyticsModel {
// ===== response atoms =====
/**
* Response atom: the series identity plus a human-readable deviceName/pointName label.
*/
public record SeriesRef(
@Schema(description = "Device id of the series") Long deviceId,
@Schema(description = "Point id of the series") Long pointId,
@@ -127,6 +172,9 @@ public final class AnalyticsModel {
// ===== responses =====
/**
* Response of {@code query_latest}: conclusion, degradation note and one item per series.
*/
public record LatestValuesResponse(
@Schema(description = "Self-contained summary of the latest values") String conclusion,
@Schema(description = "Number of value rows behind the conclusion") long sampleCount,
@@ -134,6 +182,9 @@ public final class AnalyticsModel {
@Schema(description = "Latest value per requested series") List<LatestItem> values) {
}
/**
* One series' current value; {@code hasValue} is false when no reading exists yet.
*/
public record LatestItem(
@Schema(description = "Series the value belongs to") SeriesRef series,
@Schema(description = "Raw value as captured from the device") String rawValue,
@@ -144,6 +195,9 @@ public final class AnalyticsModel {
@Schema(description = "False when the series has no reading yet") boolean hasValue) {
}
/**
* Response of {@code query_history}: points per series keyed by deviceName/pointName.
*/
public record HistoryResponse(
@Schema(description = "Self-contained summary of the returned history") String conclusion,
@Schema(description = "Points behind the conclusion") long sampleCount,
@@ -152,6 +206,9 @@ public final class AnalyticsModel {
}
/** RAW carries {@code value}; M4 carries the first/min/max/last quadruple. */
/**
* One history point — RAW carries {@code value}; M4 carries the first/min/max/last quadruple.
*/
public record HistoryPoint(
@Schema(description = "Sample time / bucket start, ISO-8601 instant") String time,
@Schema(description = "Numeric value in RAW mode") Double value,
@@ -161,6 +218,9 @@ public final class AnalyticsModel {
@Schema(description = "Last value of the bucket in M4 mode") Double last) {
}
/**
* Response of {@code compute_stats}: per-series statistical profile.
*/
public record StatsResponse(
@Schema(description = "Self-contained statistical summary") String conclusion,
@Schema(description = "Samples behind the statistics") long sampleCount,
@@ -168,6 +228,9 @@ public final class AnalyticsModel {
@Schema(description = "Profile per series, keyed by deviceName/pointName") Map<String, StatItem> stats) {
}
/**
* One series' profile: mean, population std-dev, extremes, count and requested percentiles.
*/
public record StatItem(
@Schema(description = "Series the profile belongs to") SeriesRef series,
@Schema(description = "Arithmetic mean of numeric values") Double mean,
@@ -178,6 +241,9 @@ public final class AnalyticsModel {
@Schema(description = "Requested percentiles, keyed by the percentile in [0,1]") Map<Double, Double> percentiles) {
}
/**
* Response of {@code compare_periods}: per-series current vs baseline averages.
*/
public record CompareResponse(
@Schema(description = "Self-contained comparison summary") String conclusion,
@Schema(description = "Samples behind the comparison") long sampleCount,
@@ -185,6 +251,10 @@ public final class AnalyticsModel {
@Schema(description = "Comparison per series, keyed by deviceName/pointName") Map<String, CompareItem> comparisons) {
}
/**
* One series' comparison: current, baseline, delta and percentage change (null when the
* baseline is zero).
*/
public record CompareItem(
@Schema(description = "Series the comparison belongs to") SeriesRef series,
@Schema(description = "Current window average") Double current,
@@ -195,6 +265,9 @@ public final class AnalyticsModel {
@Schema(description = "Sample count in the baseline window") long baselineCount) {
}
/**
* Response of {@code rank_entities}: ranked entities, best first.
*/
public record RankResponse(
@Schema(description = "Self-contained ranking summary") String conclusion,
@Schema(description = "Samples behind the ranking") long sampleCount,
@@ -202,6 +275,9 @@ public final class AnalyticsModel {
@Schema(description = "Ranked entities, best first") List<RankItem> ranked) {
}
/**
* One ranked entity — {@code metricValue} is set for MEAN/MAX/MIN rankings and null for ACTIVITY.
*/
public record RankItem(
@Schema(description = "Entity id of the ranked entry") String entityId,
@Schema(description = "Entity display name") String label,
@@ -209,6 +285,9 @@ public final class AnalyticsModel {
@Schema(description = "Metric value for MEAN/MAX/MIN rankings; null for ACTIVITY") Double metricValue) {
}
/**
* Response of {@code trend_analysis}: per-series trend verdicts.
*/
public record TrendResponse(
@Schema(description = "Self-contained trend summary") String conclusion,
@Schema(description = "Buckets behind the trends") long sampleCount,
@@ -216,6 +295,9 @@ public final class AnalyticsModel {
@Schema(description = "Trend per series, keyed by deviceName/pointName") Map<String, TrendItem> trends) {
}
/**
* One series' trend: least-squares slope in value-per-bucket plus total percentage change.
*/
public record TrendItem(
@Schema(description = "Series the trend belongs to") SeriesRef series,
@Schema(description = "Least-squares slope of bucket averages, in value per bucket") double slopePerBucket,
@@ -225,6 +307,9 @@ public final class AnalyticsModel {
@Schema(description = "Buckets with data in the window") int bucketCount) {
}
/**
* Response of {@code threshold_report}: per-series exceedance report.
*/
public record ThresholdResponse(
@Schema(description = "Self-contained threshold summary") String conclusion,
@Schema(description = "Exceeding samples behind the report") long sampleCount,
@@ -232,6 +317,9 @@ public final class AnalyticsModel {
@Schema(description = "Report per series, keyed by deviceName/pointName") Map<String, ThresholdItem> report) {
}
/**
* One series' exceedance summary: count, total seconds, peak value and merged intervals.
*/
public record ThresholdItem(
@Schema(description = "Series the report belongs to") SeriesRef series,
@Schema(description = "Samples beyond the threshold") long exceedCount,
@@ -240,11 +328,18 @@ public final class AnalyticsModel {
@Schema(description = "Merged exceedance intervals, at most 20 per series") List<ThresholdInterval> intervals) {
}
/**
* One merged exceedance interval [from, to], ISO-8601 instants.
*/
public record ThresholdInterval(
@Schema(description = "Interval start, ISO-8601 instant") String from,
@Schema(description = "Interval end, ISO-8601 instant") String to) {
}
/**
* Response of {@code correlate}: Pearson coefficient, aligned-bucket count and the method
* actually used (STORE vs FACADE).
*/
public record CorrelationResponse(
@Schema(description = "Self-contained correlation verdict") String conclusion,
@Schema(description = "Aligned buckets behind the coefficient") long sampleCount,
@@ -254,6 +349,10 @@ public final class AnalyticsModel {
@Schema(description = "STORE (SQL-side) or FACADE (bucketed pulls)") String method) {
}
/**
* Response of {@code data_quality_report}: coverage, silent series and sampled quality-code
* distribution.
*/
public record QualityResponse(
@Schema(description = "Self-contained quality summary") String conclusion,
@Schema(description = "Samples in the quality census") long sampleCount,
@@ -265,6 +364,9 @@ public final class AnalyticsModel {
@Schema(description = "Quality-code distribution of the sampled census, keyed by code") Map<String, Long> qualityDistribution) {
}
/**
* One series that went silent, with its last sample time.
*/
public record SilentItem(
@Schema(description = "The silent series") SeriesRef series,
@Schema(description = "Last sample time of the series, ISO-8601 instant") String lastSeen) {
@@ -58,6 +58,7 @@ public class AlertBulkConfirmVO implements Serializable {
@Schema(description = "Alert targets to apply the bulk operation to; each entry identifies a single alert row in the current tenant scope.")
private List<Item> items;
/** One (source, id) pair in the bulk confirm/reject request body. */
@Getter
@Setter
@ToString
@@ -83,6 +83,7 @@ public class AlertStatsVO implements Serializable {
@Schema(description = "24-element hourly count series for the sparkline, oldest first, always length 24")
private List<Long> sparkline24h;
/** Alert count for one alarm-type bucket of the byType distribution. */
@Getter
@Setter
@ToString
@@ -75,6 +75,7 @@ public class CoverageGapVO implements Serializable {
items.add(item);
}
/** One point that has never reported any sample (missing coverage item). */
@Getter
@Setter
@ToString
+4 -3
View File
@@ -3,8 +3,9 @@
## Overview
`dc3-common-driver` is the shared driver dependency module of the IoT DC3 platform. It provides the driver SDK shared by
all protocol drivers, including auto-registration with Manager Center, PostgreSQL-backed runtime ownership, metadata
sync, RabbitMQ command handling, durable telemetry publication, and scheduled data collection. Redis is not part of the
all protocol drivers, including auto-registration with Manager Center, manager-backed runtime ownership
(membership, assignments, and fencing tokens are persisted in PostgreSQL by the Manager Center), metadata sync,
RabbitMQ command handling, durable telemetry publication, and scheduled data collection. Redis is not part of the
driver coordination path.
## Module Information
@@ -102,5 +103,5 @@ mvn -s .mvn/settings.xml -pl dc3-common/dc3-common-driver -am test
- All `dc3-driver-*` modules — Consume this SDK
- `dc3-api-driver` — gRPC contracts consumed by this SDK
- `dc3-common-rabbitmq` — RabbitMQ exchange configuration
- `dc3-mq-core` / `dc3-mq-rabbitmq` — broker-neutral messaging port and RabbitMQ adapter
- `dc3-common-constant``RabbitConstant` routing key prefixes
+17 -1
View File
@@ -17,15 +17,31 @@ services and modules use these exceptions to signal business errors consistently
| Exception | Usage |
|----------------------|------------------------------------------|
| `ServiceException` | General service-level business error |
| `BusinessException` | Business rule violation |
| `UpdateException` | Entity update failure |
| `AddException` | Entity add failure |
| `DeleteException` | Entity delete failure |
| `NotFoundException` | Entity or resource not found |
| `DuplicateException` | Duplicate entity conflict |
| `EmptyException` | Required collection or value is empty |
| `OutRangeException` | Value outside the allowed range |
| `JsonException` | JSON serialization/deserialization error |
| `SecurityException` | General security policy violation |
| `UnAuthorizedException` | Missing or invalid authentication |
| `AccessDeniedException` | Authenticated principal lacks access |
| `DuplicateException` | Duplicate entity conflict |
| `PasswordChangeRequiredException` | A password change is enforced |
| `RegisterException` | Driver/service registration failure |
| `ConnectorException` | Device connection failure |
| `ReadPointException` | Point read failure |
| `WritePointException` | Point write failure |
| `RepositoryException` | Time-series or storage repository error |
| `RequestException` | Invalid or rejected request |
| `TypeException` | Unsupported type conversion |
| `ConfigException` | Invalid configuration |
| `CronException` | Invalid cron expression |
| `ImportException` | Import/export failure |
| `UnSupportException` | Unsupported operation or protocol |
| `AssociatedException` | Constraint violation by an association |
### Utilities
@@ -53,6 +53,7 @@ public class FacadeSystemHealthBO implements Serializable {
private FleetSummary devices;
/** Per-fleet online/total counts for one entity kind (drivers, devices). */
@Getter
@Setter
@NoArgsConstructor
+2
View File
@@ -19,6 +19,8 @@ factory and supporting services that validate tokens with the Auth Center before
| `AuthenticGatewayFilter` | Applies token validation logic; injects principal headers downstream |
| `FilterServiceImpl` | Calls Auth Center via gRPC to validate the Bearer token |
| `GatewayInitRunner` | Startup runner for gateway-specific initialization |
| `McpGatewayController` | OAuth2 authorization-server and MCP discovery endpoints exposed at the gateway |
| `McpGatewayProperties` | Binds MCP gateway settings from YAML |
## Filter Flow
+3 -2
View File
@@ -36,10 +36,11 @@ mvn -s .mvn/settings.xml -pl dc3-common/dc3-common-log -am package
## Testing
This module has no Java implementation classes or module-specific tests. Verify packaging from the repository root:
This module has no production Java implementation classes. Its tests verify the shared Logback configuration and
logging policy (`LogbackConfigurationTest`, `LoggingPolicyTest`):
```bash
mvn -s .mvn/settings.xml -pl dc3-common/dc3-common-log -am package
mvn -s .mvn/settings.xml -pl dc3-common/dc3-common-log -am test
```
## Related Modules
+1 -1
View File
@@ -20,7 +20,7 @@ Center. It is wired into `dc3-center-manager`.
| gRPC Servers (Spring `@Service`) | `DriverDriverServer`, `DriverDeviceServer`, `DriverPointServer`, `ManagerPointServer` |
| DAL Managers | `DriverManager`, `DeviceManager`, `ProfileManager`, `PointManager` (MyBatis-Plus `IService`) |
| Metadata Events | `MetadataEventPublisher`, `MetadataEventListener` — async metadata change notification via RabbitMQ |
| Scheduled Jobs | `ScheduleForManagerServiceImpl` — Quartz-based hourly statistics |
| Scheduled Jobs | `ScheduleForManagerServiceImpl` — Quartz-based hourly maintenance job (`HourlyJobForManager`) |
## gRPC Services Exposed
+4 -1
View File
@@ -33,7 +33,10 @@ Used with `@Validated(...)` in controllers:
| `Add` | Marks fields required only on creation (`@PostMapping("/add")`) |
| `Update` | Marks fields required only on update (`@PostMapping("/update")`) |
| `Select` | Marks fields for query operations |
| `Read` / `Auth` | Specialized validation groups |
| `Read` | Read-path validation (e.g. `read` requests) |
| `Write` | Write-path validation (e.g. `write` requests) |
| `Auth` | Authentication-scoped validation |
| `Check` / `Parent` / `Upload` | Specialized validation groups |
### Shared DTOs and Extensions
@@ -46,6 +46,7 @@ public class ApiExt extends BaseExt {
@Schema(description = "Extended content of the API interface; discriminated by Type and Version of the parent extension")
private Content content;
/** Human-facing API metadata (title, doc URL, remark) rendered in the tool catalog and OpenAPI quality merge. */
@Schema(description = "Extended content of the API interface")
@Getter
@Setter
@@ -47,6 +47,7 @@ public class CommandAttributeExt extends BaseExt {
@Schema(description = "Command attribute content, holding the detailed configuration of the attribute.")
private Content content;
/** Attribute-value retention strategy hint. */
@Schema(description = "Command attribute content. Detailed configuration of a command attribute, grouped into retention, UI, validation, security and applicability sections.")
@Getter
@Setter
@@ -71,6 +72,7 @@ public class CommandAttributeExt extends BaseExt {
}
/** Front-end rendering hints for the attribute input. */
@Schema(description = "UI rendering configuration. Describes how the command attribute is displayed and edited in the front-end form.", example = "form")
@Getter
@Setter
@@ -101,6 +103,7 @@ public class CommandAttributeExt extends BaseExt {
}
/** Value constraints (min/max/range) for attribute input validation. */
@Schema(description = "Validation rules. Constraints applied to the command attribute value.", example = "standard")
@Getter
@Setter
@@ -119,6 +122,7 @@ public class CommandAttributeExt extends BaseExt {
}
/** Access-control hints for the attribute. */
@Schema(description = "Security configuration. Security-related settings for the command attribute.", example = "basic")
@Getter
@Setter
@@ -131,6 +135,7 @@ public class CommandAttributeExt extends BaseExt {
}
/** Applicability scoping of the attribute (which entities/contexts it targets). */
@Schema(description = "Applicability scope. Defines the command and call types the attribute applies to.", example = "READ")
@Getter
@Setter
@@ -42,6 +42,7 @@ public class CommandExt extends BaseExt {
@Schema(description = "Extended content payload of the command metadata; carries command-specific attributes such as the reserved keep field")
private Content content;
/** Reserved command extension slot — driver/vendor-specific command metadata. */
@Schema(title = "CommandExt.Content", description = "Extended content payload for command metadata")
@Getter
@Setter
@@ -42,6 +42,7 @@ public class CommandParamExt extends BaseExt {
@Schema(description = "Extended content of the command param")
private Content content;
/** Extra command-param data not covered by typed fields. */
@Schema(description = "Extended content payload for the command param")
@Getter
@Setter
@@ -46,6 +46,7 @@ public class DeviceExt extends BaseExt {
@Schema(description = "Device extension content payload carrying type-specific configuration")
private Content content;
/** Reserved device extension slot — arbitrary custom device data that does not fit a typed field. */
@Schema(description = "Device extension content payload carrying type-specific configuration")
@Getter
@Setter
@@ -44,6 +44,7 @@ public class DriverAttributeExt extends BaseExt {
@Schema(description = "Driver attribute extension payload carrying the reserved keep field")
private Content content;
/** Attribute-value retention strategy hint for driver attributes. */
@Schema(description = "Driver attribute extension content detail")
@Getter
@Setter
@@ -44,6 +44,7 @@ public class DriverExt extends BaseExt {
@Schema(description = "Driver configuration content payload; a structured object carrying driver-specific settings, not a scalar value")
private Content content;
/** Reserved driver extension slot for driver-specific hints. */
@Schema(description = "Driver configuration content holding driver-specific settings")
@Getter
@Setter
@@ -47,6 +47,7 @@ public class EventAttributeExt extends BaseExt {
@Schema(description = "Event attribute configuration content")
private Content content;
/** Attribute-value retention strategy hint. */
@Schema(description = "Event attribute configuration content")
@Getter
@Setter
@@ -71,6 +72,7 @@ public class EventAttributeExt extends BaseExt {
}
/** Front-end rendering hints for the attribute input. */
@Schema(description = "UI rendering configuration for the attribute", example = "form")
@Getter
@Setter
@@ -101,6 +103,7 @@ public class EventAttributeExt extends BaseExt {
}
/** Value constraints for attribute input validation. */
@Schema(description = "Validation rules for the attribute value", example = "standard")
@Getter
@Setter
@@ -119,6 +122,7 @@ public class EventAttributeExt extends BaseExt {
}
/** Access-control hints for the attribute. */
@Schema(description = "Security configuration for the attribute", example = "basic")
@Getter
@Setter
@@ -131,6 +135,7 @@ public class EventAttributeExt extends BaseExt {
}
/** Applicability scoping of the attribute. */
@Schema(description = "Scope to which the attribute applies")
@Getter
@Setter
@@ -42,6 +42,7 @@ public class EventExt extends BaseExt {
@Schema(description = "Structured payload carrying the event's extension content; serialized as JSON inside the extension object")
private Content content;
/** Reserved event extension slot, currently a placeholder. */
@Schema(description = "Extended content payload nested in the event extension object")
@Getter
@Setter
@@ -42,6 +42,7 @@ public class EventParamExt extends BaseExt {
@Schema(description = "Structured event param payload carrying the reserved keep field")
private Content content;
/** Reserved event-param extension slot, currently a placeholder. */
@Schema(description = "Extended content payload of the event param")
@Getter
@Setter
@@ -48,6 +48,7 @@ public class MenuExt extends BaseExt {
@Schema(description = "Extended content, distinguished by Type and Version")
private Content content;
/** Menu display metadata: localized titles (authoritative), icon and route URL — drives the settings sidebar rendering. */
@Schema(description = "Extended content of the menu, carrying localized titles, icon, link and description")
@Getter
@Setter
@@ -49,6 +49,7 @@ public class MessageExt extends BaseExt {
@Schema(description = "Extended content payload of the message; the concrete shape is selected by the inherited type and version fields")
private Content content;
/** Message template core content (level, body text). */
@Getter
@Setter
@NoArgsConstructor
@@ -70,6 +71,7 @@ public class MessageExt extends BaseExt {
}
/** Per-channel rendering overrides keyed by channel type (FEISHU_BOT, WEBHOOK, EMAIL...). */
@Getter
@Setter
@NoArgsConstructor
@@ -44,6 +44,7 @@ public class NotifyChannelBindExt extends BaseExt {
@Schema(description = "Extended content holding notification channel binding settings")
private Content content;
/** Bind-specific extension slot (routing hints for one rule-channel pair). */
@Schema(description = "Extended content holding notification channel binding settings")
@Getter
@Setter
@@ -46,6 +46,7 @@ public class NotifyChannelExt extends BaseExt {
@Schema(description = "Non-sensitive channel configuration payload; provider-specific options and toggle flags.")
private Content content;
/** Channel-level extension slot (provider metadata for one channel). */
@Schema(description = "Extended content for a notify channel.")
@Getter
@Setter
@@ -48,6 +48,7 @@ public class NotifyExt extends BaseExt {
@Schema(description = "Extended notification content, distinguished by the type and version fields of the base extension")
private Content content;
/** Notify-rule core content. */
@Getter
@Setter
@NoArgsConstructor
@@ -93,6 +94,7 @@ public class NotifyExt extends BaseExt {
}
/** Duplicate suppression: enabled + grouping key (e.g. ruleId). */
@Getter
@Setter
@NoArgsConstructor
@@ -108,6 +110,7 @@ public class NotifyExt extends BaseExt {
}
/** Per-key send rate limiting. */
@Getter
@Setter
@NoArgsConstructor
@@ -123,6 +126,7 @@ public class NotifyExt extends BaseExt {
}
/** Quiet-window suppression of notifications. */
@Getter
@Setter
@NoArgsConstructor
@@ -138,6 +142,7 @@ public class NotifyExt extends BaseExt {
}
/** Active sending window (timezone + HH:mm start/end) outside which sends are held. */
@Getter
@Setter
@NoArgsConstructor
@@ -159,6 +164,7 @@ public class NotifyExt extends BaseExt {
}
/** Repeat-reminder policy while a condition stays true. */
@Getter
@Setter
@NoArgsConstructor
@@ -177,6 +183,7 @@ public class NotifyExt extends BaseExt {
}
/** Recovery-notification policy when a condition clears. */
@Getter
@Setter
@NoArgsConstructor
@@ -195,6 +202,7 @@ public class NotifyExt extends BaseExt {
}
/** Escalation policy when a condition stays unacknowledged. */
@Getter
@Setter
@NoArgsConstructor
@@ -44,6 +44,7 @@ public class NotifyHistoryRequestExt extends BaseExt {
@Schema(description = "Rendered notification content carried by the delivery request")
private Content content;
/** Request payload schema extension for notify history calls. */
@Getter
@Setter
@NoArgsConstructor
@@ -44,6 +44,7 @@ public class NotifyHistoryResponseExt extends BaseExt {
@Schema(description = "Extended content describing the channel provider response of a notification delivery")
private Content content;
/** Response payload schema extension for notify history calls. */
@Getter
@Setter
@NoArgsConstructor
@@ -46,6 +46,7 @@ public class PointAttributeExt extends BaseExt {
@Schema(description = "Extended content payload, distinguished by the type and version fields of the base extension")
private Content content;
/** Attribute-value retention strategy hint for point attributes. */
@Getter
@Setter
@NoArgsConstructor
@@ -46,6 +46,7 @@ public class PointExt extends BaseExt {
@Schema(description = "Extended content payload of the point; the shape is discriminated by the type and version fields inherited from the owning point's tenant scope")
private Content content;
/** Reserved point extension slot — custom point data beyond typed fields. */
@Getter
@Setter
@NoArgsConstructor
@@ -46,6 +46,7 @@ public class ProfileExt extends BaseExt {
@Schema(description = "Extended content payload of the profile/template; interpretation is driven by the type and version fields of the parent BaseExt")
private Content content;
/** Reserved profile extension slot, currently a placeholder kept for forward compatibility. */
@Getter
@Setter
@NoArgsConstructor
@@ -46,6 +46,7 @@ public class ResourceExt extends BaseExt {
@Schema(description = "Extended content, distinguished by the type and version fields inherited from the base extension")
private Content content;
/** Resource extension slot (permission metadata beyond the typed resource fields). */
@Getter
@Setter
@NoArgsConstructor
@@ -46,6 +46,7 @@ public class RoleExt extends BaseExt {
@Schema(description = "Role extension payload; its shape is distinguished by the Type and Version fields of the base extension")
private Content content;
/** Reserved role extension slot. */
@Getter
@Setter
@NoArgsConstructor
@@ -45,6 +45,7 @@ public class RuleAlarmEventExt extends BaseExt {
@Schema(description = "Extended content holding the rule alarm event snapshot")
private Content content;
/** Alarm-event payload schema extension emitted by rule evaluation. */
@Getter
@Setter
@NoArgsConstructor
@@ -49,6 +49,7 @@ public class RuleExt extends BaseExt {
@Schema(description = "Extended content, which can be distinguished by type and version.")
private Content content;
/** Alarm rule core content. */
@Getter
@Setter
@NoArgsConstructor
@@ -94,6 +95,7 @@ public class RuleExt extends BaseExt {
}
/** The match predicate: field + operator + expected/threshold operands. */
@Getter
@Setter
@NoArgsConstructor
@@ -145,6 +147,7 @@ public class RuleExt extends BaseExt {
}
/** Time-window scoping of the rule evaluation. */
@Getter
@Setter
@NoArgsConstructor
@@ -172,6 +175,7 @@ public class RuleExt extends BaseExt {
}
/** Recovery (condition-clear) behavior of the rule. */
@Getter
@Setter
@NoArgsConstructor
@@ -45,6 +45,7 @@ public class RuleStateExt extends BaseExt {
@Schema(description = "Extended content holding the rule runtime state snapshot")
private Content content;
/** Rule-runtime state extension (per-rule evaluation state metadata). */
@Getter
@Setter
@NoArgsConstructor
@@ -46,6 +46,7 @@ public class TenantExt extends BaseExt {
@Schema(description = "Extended content payload for the tenant; structure is interpreted according to the type and version fields of the extension")
private Content content;
/** Reserved tenant extension slot. */
@Getter
@Setter
@NoArgsConstructor
@@ -46,6 +46,7 @@ public class UserIdentityExt extends BaseExt {
@Schema(description = "Extended content payload, distinguished by the type and version fields")
private Content content;
/** User identity extension slot (credential/profile metadata per identity). */
@Getter
@Setter
@NoArgsConstructor
@@ -46,6 +46,7 @@ public class UserSocialExt extends BaseExt {
@Schema(description = "Extended content, distinguished by Type and Version")
private Content content;
/** User social-binding extension slot (third-party account metadata). */
@Getter
@Setter
@NoArgsConstructor
+18 -8
View File
@@ -24,22 +24,32 @@ the MQTT driver and any service requiring MQTT connectivity.
## Configuration Properties
Configure in `application*.yml` under the `dc3.driver.mqtt` prefix:
Configure in `application*.yml` under the `dc3.driver.mqtt` prefix. The shared `application-mqtt.yml` ships
these defaults:
```yaml
dc3:
driver:
mqtt:
url: tcp://${MQTT_BROKER_HOST:dc3-rabbitmq}:${MQTT_BROKER_PORT:2883}
auth-type: USERNAME # NONE | USERNAME | CLIENT
username: ${MQTT_USERNAME:dc3}
password: ${MQTT_PASSWORD:}
ca-crt: classpath:/certs/ca.crt
client-crt: classpath:/certs/client.crt
client-key: classpath:/certs/client.key
client-key-pass: dc3-client
topic-prefix: dc3/${dc3.driver.tenant}/${spring.application.name}/
receive-topics:
- name: data
qos: 1
default-send-topic:
qos: 1
name: command
keep-alive: 15
completion-timeout: 3000
batch:
speed: ${MQTT_BATCH_SPEED:100}
interval: ${MQTT_BATCH_INTERVAL:5}
```
Connection settings such as `url` (dev default `tcp://${MQTT_BROKER_HOST:dc3-rabbitmq}:${MQTT_BROKER_PORT:2883}`),
`auth-type` (`NONE` | `USERNAME` | `CLIENT`), `username`, `password`, and `receive-topics` are supplied by the
consumer application (e.g. `dc3-driver-mqtt`).
## Usage
This module is activated when the `mqtt` profile is included or MQTT-related auto-configuration is on the classpath. The
@@ -37,6 +37,7 @@ public class RequestHeader {
throw new IllegalStateException(ExceptionConstant.UTILITY_CLASS);
}
/** X-Auth-Token envelope: cipher + salt the gateway re-validates per hop. */
@Getter
@Setter
@NoArgsConstructor
@@ -55,6 +56,7 @@ public class RequestHeader {
}
/** X-Auth-Principal envelope: authenticated identity (principal/tenant) for downstream authz and audit. */
@Getter
@Setter
@NoArgsConstructor
+2 -1
View File
@@ -47,4 +47,5 @@ mvn -s .mvn/settings.xml -pl dc3-common/dc3-common-quartz -am test
## Related Modules
- `dc3-common-manager` — Uses `QuartzService` for hourly data-volume statistics jobs
- `dc3-common-manager` — Uses `QuartzService` for its hourly maintenance job
- `dc3-common-data` / `dc3-common-mqtt` / `dc3-common-driver` — periodic persistence, message, and scheduling tasks
+5 -2
View File
@@ -19,6 +19,9 @@ REST-based center services.
| `WebFluxSecurityConfig` | Security chain, public-path rules, and facade-backed authorization |
| `RequestIdWebFilter` | Adds and propagates request IDs for tracing |
| `ExceptionConfig` | `@ControllerAdvice` global exception handler mapping exceptions to `R<T>` responses |
| `BaseController` | Reactive controller helpers plus user/tenant context resolution |
| `PrincipalHeaderUtil` | Reads the signed principal headers injected by the gateway |
| `SpringDocConfig` | Shared springdoc/OpenAPI group configuration |
| `ResponseUtil` | Utilities for writing non-controller `ServerHttpResponse` bodies in reactive context |
## Exception Handling
@@ -28,8 +31,8 @@ All exceptions thrown by controllers are caught by `ExceptionConfig` and mapped
```json
{
"ok": false,
"code": "FAILURE",
"message": "Resource not found"
"code": "R500",
"message": "Service exception"
}
```
+35
View File
@@ -0,0 +1,35 @@
# DC3 DB
dc3-db is the relational dialect layer of IoT DC3. It provides dialect-neutral JDBC infrastructure — MyBatis-Plus
wiring, the tenant-line handler, and generator utilities — plus one adapter per supported database engine behind the
dc3.db.type property.
## Modules
| Module | Role |
|---|---|
| dc3-db-core | dialect-neutral JDBC infrastructure: MybatisPlusConfig, TenantLineHandlerImpl, MybatisUtil, profile wiring |
| dc3-db-postgres | PostgreSQL dialect adapter (default): driver, timestamptz type handler, pagination DbType |
| dc3-db-mysql | MySQL 8 dialect adapter: driver, DATETIME(6) UTC conventions |
| dc3-db-mariadb | MariaDB 10.6+ dialect adapter — MySQL-compatible surface except ODKU row aliases (uses VALUES()) |
| dc3-db-tck | dual-dialect relational contract suite — identical mapper-level assertions against PostgreSQL and MySQL |
## Selection
The active dialect is chosen by the dc3.db.type property (default postgres). Only the selected adapter's
auto-configuration is active.
```yaml
dc3:
db:
type: postgres
```
## Build and verify
```bash
mvn -s .mvn/settings.xml -q -f dc3-db/pom.xml -DskipTests compile
mvn -s .mvn/settings.xml -f dc3-db/pom.xml test
```
Dialect conventions and migration notes live in docs/db-dialects.md.
@@ -84,6 +84,7 @@ public class MybatisPlusConfig {
return provider;
}
/** Tenant-line before pagination; dialect pagination DbType injected by the db adapter. */
@Bean
@ConditionalOnMissingBean
public MybatisPlusInterceptor mybatisPlusInterceptor(TenantLineHandler tenantLineHandler) {
@@ -36,6 +36,7 @@ import org.springframework.context.annotation.Bean;
@ConditionalOnProperty(prefix = "dc3.db", name = "type", havingValue = "mariadb")
public class MariadbDbAutoConfiguration {
/** MyBatis-Plus pagination dialect for MariaDB. */
@Bean
public DbType paginationDbType() {
return DbType.MARIADB;
@@ -35,6 +35,7 @@ import org.springframework.context.annotation.Bean;
@ConditionalOnProperty(prefix = "dc3.db", name = "type", havingValue = "mysql")
public class MysqlDbAutoConfiguration {
/** MyBatis-Plus pagination dialect for MySQL. */
@Bean
public DbType paginationDbType() {
return DbType.MYSQL;
@@ -35,6 +35,7 @@ import org.springframework.context.annotation.Bean;
@ConditionalOnProperty(prefix = "dc3.db", name = "type", havingValue = "postgres", matchIfMissing = true)
public class PostgresDbAutoConfiguration {
/** MyBatis-Plus pagination dialect for PostgreSQL. */
@Bean
public DbType paginationDbType() {
return DbType.POSTGRE_SQL;
@@ -61,7 +61,7 @@ import java.util.concurrent.TimeUnit;
*
* <p>
* <b>WARNING:</b> This driver is a work-in-progress skeleton. Protocol-level
* I/O is not yet fully implemented — see TODO markers in method bodies.
* I/O is not yet fully implemented.
* </p>
*
* @author pnoker
@@ -66,6 +66,7 @@ public class CoapProperties {
private Dtls dtls = new Dtls();
@NoArgsConstructor
/** CoAP role of the endpoint: client (collect from devices), server (accept device pushes), or both. */
public enum ModeEnum {
CLIENT, SERVER, BOTH,
@@ -74,6 +75,7 @@ public class CoapProperties {
@Getter
@Setter
/** DTLS credentials: PSK identity/secret or a trust-store path for certificate mode. */
public static class Dtls {
private String pskIdentity;
+12
View File
@@ -37,9 +37,21 @@ one native `Runtime` + TCP `MasterChannel` + association per outstation, class 0
| Point Index | pointIndex | INT | 0 | DNP3 point index within the selected point type |
| Point Type | pointType | STRING | BINARY_INPUT | BINARY_INPUT, ANALOG_INPUT, COUNTER, DOUBLE_BIT_BINARY_INPUT, BINARY_OUTPUT, or ANALOG_OUTPUT |
## Command Attributes (write)
| Attribute | Code | Type | Default | Description |
|-----------|------|------|---------|-------------|
| Point Index | pointIndex | INT | 0 | DNP3 point index within the selected output point type |
| Point Type | pointType | STRING | BINARY_OUTPUT | BINARY_OUTPUT or ANALOG_OUTPUT |
The module `application.yml` is authoritative for attribute codes, types, defaults, scheduling, health, and local
buffering. Keep this README aligned when those user-facing settings change.
## Prerequisites
A reachable DNP3 outstation over TCP (default port 20000) with matching master/outstation link-layer addresses. Native
library loading must be verified on the target platform before commissioning.
## Running Locally
```bash
+16 -16
View File
@@ -20,28 +20,28 @@ polling for IEC 104 client connections.
## Driver Attributes (Device-level)
| Attribute | Description |
|-----------------|-------------|
| Host | |
| Port | |
| ASDU Address | |
| COT Length | |
| CA Length | |
| IOA Length | |
| Connect Timeout | |
| Attribute | Code | Type | Default | Description |
|-----------------|----------------|--------|-----------|--------------------------------------|
| Host | host | STRING | localhost | IEC 104 server address |
| Port | port | INT | 2404 | IEC 104 TCP service port |
| ASDU Address | asduAddress | INT | 1 | Common ASDU address |
| COT Length | cotLength | INT | 2 | Cause-of-transmission field length |
| CA Length | caLength | INT | 2 | Common-address field length |
| IOA Length | ioaLength | INT | 3 | Information-object-address field length |
| Connect Timeout | connectTimeout | INT | 10000 | Connection timeout in milliseconds |
## Point Attributes
| Attribute | Description |
|-----------|-------------|
| IOA | |
| ASDU Type | |
| Attribute | Code | Type | Default | Description |
|-----------|----------|--------|----------|----------------------------------|
| IOA | ioa | INT | 0 | Information object address |
| ASDU Type | asduType | STRING | M_ME_NC_1 | ASDU type (e.g. M_ME_NC_1) |
## Command Attributes (write)
| Attribute | Description |
|--------------|-------------|
| Send Command | |
| Attribute | Code | Type | Default | Description |
|--------------|-------------|--------|----------|--------------------------|
| Send Command | sendCommand | STRING | ${value} | Command payload template |
The module `application.yml` is authoritative for attribute codes, types, default values, scheduling, health, and
local buffering. Keep this README aligned when those user-facing settings change.
+10
View File
@@ -26,9 +26,19 @@ per IED device and reads/writes data attributes addressed by an object reference
| Object Reference | objectReference | STRING | | Data object reference, e.g. S1MMXU1.TotW.actVal |
| Functional Constraint | functionalConstraint | STRING | MX | Functional constraint, e.g. MX, ST, CO, SP, SE |
## Command Attributes (write)
| Attribute | Code | Type | Default | Description |
|------------------|-----------------|--------|---------|--------------------------------------------------------|
| Object Reference | objectReference | STRING | | Data object reference to write, e.g. S1MMXU1.TotW.actVal |
The module `application.yml` is authoritative for attribute codes, types, defaults, scheduling, health, and local
buffering. Keep this README aligned when those user-facing settings change.
## Prerequisites
A reachable IEC 61850 server (IED) exposing the configured data objects, typically on MMS port 102.
## Running Locally
```bash
+10
View File
@@ -25,9 +25,19 @@ latest cached value, and a point write produces a message to the configured topi
| Topic | topic | STRING | | Override topic (defaults to driver topic) |
| Key | key | STRING | | Message key used for produce and cache lookup |
## Command Attributes (write)
| Attribute | Code | Type | Default | Description |
|-----------|-------|--------|---------|------------------------------------------|
| Topic | topic | STRING | | Topic to produce the write message to |
The module `application.yml` is authoritative for attribute codes, types, defaults, scheduling, health, and local
buffering. Keep this README aligned when those user-facing settings change.
## Prerequisites
A reachable Kafka cluster. Point the `spring.kafka.*` properties (or `KAFKA_BOOTSTRAP_SERVERS`) at the target broker.
## Connection
The broker connection is configured through Spring Boot `spring.kafka.*` properties in `application.yml`, driven by
+10
View File
@@ -30,9 +30,19 @@ control values.
| Data Type | dataType | STRING | BOOL | BOOL, UINT, FLOAT, or CONTROL |
| DPT | dpt | STRING | | Datapoint type for UINT reads/writes, e.g. 5.001 |
## Command Attributes (write)
| Attribute | Code | Type | Default | Description |
|---------------|--------------|--------|---------|--------------------------------------|
| Group Address | groupAddress | STRING | | KNX group address to write to |
The module `application.yml` is authoritative for attribute codes, types, defaults, scheduling, health, and local
buffering. Keep this README aligned when those user-facing settings change.
## Prerequisites
A KNX installation reachable through a KNX IP gateway (default port 3671) with tunneling enabled.
## Running Locally
```bash
@@ -59,4 +59,4 @@ mvn -s .mvn/settings.xml -pl dc3-driver/dc3-driver-listening-virtual -am test
## Related Modules
- `dc3-common-driver` — Driver SDK for registration and RabbitMQ integration
- `dc3-common-driver` — Driver SDK for registration, scheduling, and RabbitMQ integration
+11
View File
@@ -30,9 +30,20 @@ the ChirpStack `command/down` topic.
| DevEUI | devEui | STRING | | LoRaWAN device EUI (16 hex characters) |
| Field | field | STRING | | Cayenne LPP object field; empty returns raw base64 |
## Command Attributes (write)
| Attribute | Code | Type | Default | Description |
|-----------|--------|--------|---------|--------------------------------------|
| DevEUI | devEui | STRING | | Target device EUI for the downlink |
The module `application.yml` is authoritative for attribute codes, types, defaults, scheduling, health, and local
buffering. Keep this README aligned when those user-facing settings change.
## Prerequisites
A ChirpStack instance publishing uplinks to the configured MQTT broker topic
(`application/+/device/+/event/up`).
## Running Locally
```bash
+6
View File
@@ -32,6 +32,12 @@ record by index and decoding it as BCD, integer, IEEE-754, or raw HEX.
| Record Index | recordIndex | INT | 0 | Zero-based index of the DIF/VIF record |
| Data Format | dataFormat | STRING | AUTO | Data format: AUTO, HEX, BCD, INT, FLOAT |
## Command Attributes (write)
| Attribute | Code | Type | Default | Description |
|-------------|------------|--------|---------|------------------------------------------------|
| Data Format | dataFormat | STRING | ASCII | Format used to encode the written value |
The module `application.yml` is authoritative for attribute codes, types, defaults, scheduling, health, and local
buffering. Keep this README aligned when those user-facing settings change.
+1 -1
View File
@@ -79,5 +79,5 @@ mvn -s .mvn/settings.xml -pl dc3-driver/dc3-driver-mqtt -am test
## Related Modules
- `dc3-common-driver` — Driver SDK for registration and RabbitMQ integration
- `dc3-common-driver` — Driver SDK for registration, scheduling, and RabbitMQ integration
- `dc3-common-mqtt` — MQTT client configuration and utilities
@@ -62,7 +62,7 @@ import java.util.Objects;
*
* <p>
* <b>WARNING:</b> This driver is a work-in-progress skeleton. Protocol-level
* I/O is not yet fully implemented — see TODO markers in method bodies.
* I/O is not yet fully implemented.
* </p>
*
* @author pnoker
@@ -68,7 +68,7 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
*
* <p>
* <b>WARNING:</b> This driver is a work-in-progress skeleton. Protocol-level
* I/O is not yet fully implemented — see TODO markers in method bodies.
* I/O is not yet fully implemented.
* </p>
*
* @author pnoker
@@ -48,6 +48,7 @@ public class FILETIME {
this.low = low;
}
/** The COM wire shape: a two-int JIStruct (low, high), member order fixed by the OPC spec. */
public static JIStruct getStruct() throws JIException {
final JIStruct struct = new JIStruct();
@@ -57,6 +58,7 @@ public class FILETIME {
return struct;
}
/** Decode a wire FILETIME (low first) into the typed wrapper. */
public static FILETIME fromStruct(final JIStruct struct) {
final FILETIME ft = new FILETIME();
@@ -112,6 +114,7 @@ public class FILETIME {
return true;
}
/** The FILETIME epoch (1601-01-01) reinterpreted as a Calendar. */
public Calendar asCalendar() {
final Calendar c = Calendar.getInstance();
@@ -53,6 +53,7 @@ public class OPCITEMRESULT {
return struct;
}
/** Decode the COM wire struct into the typed OPCITEMRESULT wrapper. */
public static OPCITEMRESULT fromStruct(final JIStruct struct) {
OPCITEMRESULT result = new OPCITEMRESULT();
@@ -52,6 +52,7 @@ public class OPCITEMSTATE {
return struct;
}
/** Decode the COM wire struct into the typed OPCITEMSTATE wrapper. */
public static OPCITEMSTATE fromStruct(final JIStruct struct) {
OPCITEMSTATE itemState = new OPCITEMSTATE();
@@ -72,6 +72,7 @@ public class OPCSERVERSTATUS {
return struct;
}
/** Decode the COM wire struct into the typed OPCSERVERSTATUS wrapper. */
public static OPCSERVERSTATUS fromStruct(final JIStruct struct) {
OPCSERVERSTATUS status = new OPCSERVERSTATUS();
+2 -2
View File
@@ -16,8 +16,8 @@ servers to read and write node values from industrial automation systems using t
| Attribute | Description |
|-----------|--------------------------------------|
| Host | OPC UA server hostname or IP |
| Port | OPC UA server port (typically 4840) |
| Path | OPC UA endpoint path (e.g., `/milo`) |
| Port | OPC UA server port (default 18600) |
| Path | OPC UA endpoint path (default `/`) |
## Point Attributes
+11
View File
@@ -26,9 +26,20 @@ logical devices within one Redis instance.
| Data Type | dataType | STRING | STRING | Redis data type: STRING or HASH |
| Field | field | STRING | | Hash field, required when dataType=HASH |
## Command Attributes (write)
| Attribute | Code | Type | Default | Description |
|-----------|----------|--------|---------|------------------------------------------------|
| Data Type | dataType | STRING | STRING | Redis data type of the written key: STRING or HASH |
The module `application.yml` is authoritative for attribute codes, types, defaults, scheduling, health, and local
buffering. Keep this README aligned when those user-facing settings change.
## Prerequisites
A reachable Redis instance. Point the `spring.data.redis.*` properties (or `REDIS_HOST`, `REDIS_PORT`,
`REDIS_PASSWORD`, `REDIS_DATABASE`) at the target server.
## Connection
The Redis connection is configured through Spring Boot `spring.data.redis.*` properties in `application.yml`, driven
+1 -1
View File
@@ -12,7 +12,7 @@ hardware.
- **Artifact ID**: dc3-driver-virtual
- **Driver Name**: Virtual Driver
## Driver Attributes
## Driver Attributes (Device-level)
| Attribute | Description |
|-----------|---------------------------|
+1
View File
@@ -22,6 +22,7 @@ storage, RabbitMQ delivery, and TimescaleDB hypertables.
| `EventReportE2eIT` | Event report end-to-end |
| `RabbitDeliveryIT` | RabbitMQ message delivery |
| `PostgresHypertableIT` | TimescaleDB hypertable behavior |
| `MqttVendorNeutralityIT` | Broker-neutral MQTT delivery across vendors |
| `E2eEnvironmentGuardIT` | Guards that the target environment is configured |
The harness (`E2eStack`, `BaseE2eIT`) lives under `src/test/java/io/github/pnoker/e2e/harness`.
+3 -3
View File
@@ -14,9 +14,9 @@ routing, and reverse proxying.
## Service Ports
| Protocol | Port |
|----------|--------|
| HTTP | `8000` |
| Protocol | Port | Override |
|----------|--------|--------------------|
| HTTP | `8000` | `DC3_GATEWAY_PORT` |
## Key Responsibilities
+42
View File
@@ -0,0 +1,42 @@
# DC3 MQ
dc3-mq is the pluggable message-broker layer of IoT DC3. It defines a broker-neutral messaging port — logical topics,
subscriptions, delivery envelopes, and capability negotiation — plus one certified adapter per supported broker.
Business code (centers, drivers, common modules) depends on the port and never on broker-specific classes.
## Modules
| Module | Role |
|---|---|
| dc3-mq-core | broker-neutral port: BrokerAdapter SPI, Dc3Listener/MqListener annotations, MessageSender, envelopes, retry policy, and batch-consumer properties (dc3.data.point.batch) |
| dc3-mq-rabbitmq | RabbitMQ adapter (default); physical topology is byte-for-byte identical to the legacy RabbitConstant names |
| dc3-mq-kafka | Kafka adapter |
| dc3-mq-rocketmq | RocketMQ adapter |
| dc3-mq-pulsar | Pulsar adapter |
| dc3-mq-activemq | ActiveMQ (Artemis / Classic, JMS 2.0) adapter |
| dc3-mq-mqtt | MQTT 5 adapter (EMQX / HiveMQ / NanoMQ / ...) |
| dc3-mq-tck | broker-neutral contract suite: an adapter that passes these tests is compliant |
## Selection
The active broker is chosen by the dc3.mq.type property (default rabbitmq). Only the selected adapter's
auto-configuration is active; the others remain dormant on the classpath.
```yaml
dc3:
mq:
type: rabbitmq
```
Logical topics (MqTopic) are mapped to broker-specific exchange/queue/topic names by each adapter; the RabbitMQ adapter
keeps the legacy names such as dc3.e.value and dc3.q.value.point, so existing deployments and the dc3.rabbit.tag
prefixing behaviour keep working unchanged.
## Build and verify
```bash
mvn -s .mvn/settings.xml -q -f dc3-mq/pom.xml -DskipTests compile
mvn -s .mvn/settings.xml -f dc3-mq/pom.xml test
```
Adapter selection guides and trade-offs live in docs/mq-brokers.md.
@@ -38,6 +38,7 @@ import org.springframework.context.annotation.Bean;
@ConditionalOnProperty(prefix = "dc3.mq", name = "type", havingValue = "activemq")
public class ActiveMqAdapterConfiguration {
/** Connection factory from the adapter url/credentials; overridable by a user bean. */
@Bean
@ConditionalOnMissingBean(jakarta.jms.ConnectionFactory.class)
public ActiveMQConnectionFactory activeMqConnectionFactory(
@@ -47,6 +48,7 @@ public class ActiveMqAdapterConfiguration {
return new ActiveMQConnectionFactory(url, user, password);
}
/** The port adapter bound to this broker's connection factory. */
@Bean
public ActiveMqAdapter activeMqAdapter(jakarta.jms.ConnectionFactory connectionFactory,
BatchConsumerProperties batchProperties) {
@@ -44,6 +44,7 @@ import java.util.Objects;
@EnableConfigurationProperties(BatchConsumerProperties.class)
public class MqAutoConfiguration {
/** Publishing facade over the active adapter. */
@Bean
@ConditionalOnMissingBean(MessageSender.class)
public MessageSender messageSender(ObjectProvider<BrokerAdapter> adapterProvider) {
@@ -63,6 +64,7 @@ public class MqAutoConfiguration {
return new MessageSenderImpl(adapter);
}
/** Registers beans carrying @Dc3Listener methods with the active adapter. */
@Bean
@ConditionalOnMissingBean
public Dc3ListenerProcessor dc3ListenerProcessor(ObjectProvider<BrokerAdapter> adapterProvider) {
@@ -63,6 +63,7 @@ public class MqMessage {
@Builder.Default
private final Duration delay = Duration.ZERO;
/** Build a message for a topic with an explicit partition key (ordered streams). */
public static MqMessage of(MqTopic topic, String partitionKey, Object payload) {
return MqMessage.builder()
.topic(topic)
@@ -40,6 +40,7 @@ import java.util.Map;
@ConditionalOnProperty(prefix = "dc3.mq", name = "type", havingValue = "kafka")
public class KafkaMqAdapterConfiguration {
/** Producer template on the adapter bootstrap servers; overridable by a user bean. */
@Bean
@ConditionalOnMissingBean(KafkaTemplate.class)
public KafkaTemplate<String, byte[]> kafkaMqTemplate(
@@ -47,6 +48,7 @@ public class KafkaMqAdapterConfiguration {
return KafkaMqAdapter.template(bootstrapServers);
}
/** The port adapter bound to the Kafka template. */
@Bean
public KafkaMqAdapter kafkaMqAdapter(KafkaTemplate<String, byte[]> kafkaTemplate,
@Value("${dc3.mq.kafka.bootstrap-servers:${DC3_MQ_KAFKA_BOOTSTRAP:${spring.kafka.bootstrap-servers:localhost:9092}}}")
@@ -36,6 +36,7 @@ import org.springframework.context.annotation.Bean;
@ConditionalOnProperty(prefix = "dc3.mq", name = "type", havingValue = "mqtt")
public class MqttMqAdapterConfiguration {
/** The port adapter over the HiveMQ MQTT5 client. */
@Bean
public MqttMqAdapter mqttMqAdapter(@Value("${dc3.mq.mqtt.host:localhost}") String host,
@Value("${dc3.mq.mqtt.port:1883}") int port,
@@ -38,6 +38,7 @@ import org.springframework.context.annotation.Bean;
@ConditionalOnProperty(prefix = "dc3.mq", name = "type", havingValue = "pulsar")
public class PulsarMqAdapterConfiguration {
/** Shared Pulsar client on the service url. */
@Bean(destroyMethod = "close")
@ConditionalOnMissingBean(PulsarClient.class)
public PulsarClient pulsarClient(
@@ -46,6 +47,7 @@ public class PulsarMqAdapterConfiguration {
return PulsarClient.builder().serviceUrl(serviceUrl).build();
}
/** The port adapter bound to the shared client. */
@Bean
public PulsarMqAdapter pulsarMqAdapter(PulsarClient pulsarClient, BatchConsumerProperties batchProperties) {
return new PulsarMqAdapter(pulsarClient, batchProperties);
@@ -43,10 +43,12 @@ public final class RabbitAcknowledgment implements Acknowledgment {
this.multiple = multiple;
}
/** Ack exactly one delivery ({@code multiple=false}). */
public static RabbitAcknowledgment single(Channel channel, long deliveryTag) {
return new RabbitAcknowledgment(channel, deliveryTag, false);
}
/** Ack everything up to the tag ({@code multiple=true}) — the broker-batch commit path. */
public static RabbitAcknowledgment batch(Channel channel, long lastDeliveryTag) {
return new RabbitAcknowledgment(channel, lastDeliveryTag, true);
}
@@ -47,12 +47,14 @@ import org.springframework.context.annotation.Bean;
@ConditionalOnProperty(prefix = "dc3.mq", name = "type", havingValue = "rabbitmq", matchIfMissing = true)
public class RabbitMqAdapterConfiguration {
/** JSON converter with typed envelope headers. */
@Bean
@ConditionalOnMissingBean
public MessageConverter messageConverter() {
return new JacksonJsonMessageConverter(JsonUtil.getJsonMapper());
}
/** Publisher-confirms template with mandatory returns. */
@Bean(name = "rabbitTemplate")
@ConditionalOnMissingBean(RabbitTemplate.class)
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory, MessageConverter messageConverter) {
@@ -79,12 +81,14 @@ public class RabbitMqAdapterConfiguration {
return rabbitTemplate;
}
/** Declares queues/exchanges/bindings at startup. */
@Bean
@ConditionalOnMissingBean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
return new RabbitAdmin(connectionFactory);
}
/** The port adapter bound to the template and admin. */
@Bean
public RabbitMqAdapter rabbitMqAdapter(RabbitTemplate rabbitTemplate, RabbitAdmin rabbitAdmin,
ConnectionFactory connectionFactory, BatchConsumerProperties batchProperties,

Some files were not shown because too many files have changed in this diff Show More