Add unified event processing sources
This commit is contained in:
parent
dd0ccae290
commit
04d7bf513e
|
|
@ -0,0 +1,389 @@
|
||||||
|
package at.procon.eventhub.persistence;
|
||||||
|
|
||||||
|
import at.procon.eventhub.dto.DriverCardRefDto;
|
||||||
|
import at.procon.eventhub.dto.DriverRefDto;
|
||||||
|
import at.procon.eventhub.dto.EventDetailsDto;
|
||||||
|
import at.procon.eventhub.dto.EventDomain;
|
||||||
|
import at.procon.eventhub.dto.EventHubEventDto;
|
||||||
|
import at.procon.eventhub.dto.EventHubPackageRequest;
|
||||||
|
import at.procon.eventhub.dto.EventLifecycle;
|
||||||
|
import at.procon.eventhub.dto.EventSourceDto;
|
||||||
|
import at.procon.eventhub.dto.EventType;
|
||||||
|
import at.procon.eventhub.dto.GeoPointDto;
|
||||||
|
import at.procon.eventhub.dto.ImportScopeDto;
|
||||||
|
import at.procon.eventhub.dto.ImportScopeType;
|
||||||
|
import at.procon.eventhub.dto.SourceGroupRefDto;
|
||||||
|
import at.procon.eventhub.dto.SourceGroupType;
|
||||||
|
import at.procon.eventhub.dto.SourcePackageRefDto;
|
||||||
|
import at.procon.eventhub.dto.VehicleRefDto;
|
||||||
|
import at.procon.eventhub.dto.VehicleRegistrationRefDto;
|
||||||
|
import at.procon.eventhub.processing.model.UnifiedDriverEventsRequest;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public class EventHubEventReadRepository {
|
||||||
|
|
||||||
|
private final JdbcTemplate jdbcTemplate;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
public EventHubEventReadRepository(JdbcTemplate jdbcTemplate, ObjectMapper objectMapper) {
|
||||||
|
this.jdbcTemplate = jdbcTemplate;
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<EventHubEventDto> findEvents(
|
||||||
|
UnifiedDriverEventsRequest request,
|
||||||
|
String providerKey,
|
||||||
|
List<String> sourceKinds
|
||||||
|
) {
|
||||||
|
StringBuilder sql = new StringBuilder(
|
||||||
|
"""
|
||||||
|
select
|
||||||
|
event.id,
|
||||||
|
event.external_source_event_id,
|
||||||
|
event.occurred_at,
|
||||||
|
event.received_partner_at,
|
||||||
|
event.received_hub_at,
|
||||||
|
event.event_domain,
|
||||||
|
event.event_type,
|
||||||
|
event.lifecycle,
|
||||||
|
event.odometer_m,
|
||||||
|
ST_Y(event.position::geometry) as latitude,
|
||||||
|
ST_X(event.position::geometry) as longitude,
|
||||||
|
event.payload,
|
||||||
|
event.manual_entry,
|
||||||
|
source.provider_key,
|
||||||
|
source.source_kind,
|
||||||
|
source.source_key,
|
||||||
|
source.source_instance_key,
|
||||||
|
source.tenant_provider_setting_key,
|
||||||
|
source.external_fleet_key,
|
||||||
|
package.id as data_package_id,
|
||||||
|
package.tenant_key,
|
||||||
|
package.event_family,
|
||||||
|
package.business_date,
|
||||||
|
package.external_package_id,
|
||||||
|
package.source_group_type,
|
||||||
|
package.source_group_entity_id,
|
||||||
|
package.source_group_code,
|
||||||
|
package.source_group_name,
|
||||||
|
package.import_scope_type,
|
||||||
|
package.root_source_org_entity_id,
|
||||||
|
package.root_source_org_code,
|
||||||
|
package.root_source_org_name,
|
||||||
|
package.include_children,
|
||||||
|
package.occurred_from as package_occurred_from,
|
||||||
|
package.occurred_to as package_occurred_to,
|
||||||
|
package.source_package_kind as package_source_package_kind,
|
||||||
|
package.source_package_period_from,
|
||||||
|
package.source_package_period_to,
|
||||||
|
package.source_package_imported_at,
|
||||||
|
event.source_package_id,
|
||||||
|
event.source_package_entity_id,
|
||||||
|
detail.detail_type,
|
||||||
|
detail.attributes,
|
||||||
|
driver.source_driver_entity_id,
|
||||||
|
driver_card.nation as driver_card_nation,
|
||||||
|
driver_card.card_number as driver_card_number,
|
||||||
|
vehicle.source_vehicle_entity_id,
|
||||||
|
vehicle.vin,
|
||||||
|
registration.source_registration_entity_id,
|
||||||
|
registration.nation as vehicle_registration_nation,
|
||||||
|
registration.registration_number as vehicle_registration_number,
|
||||||
|
event.driver_id,
|
||||||
|
event.vehicle_id,
|
||||||
|
event.vehicle_registration_id
|
||||||
|
from eventhub.event event
|
||||||
|
join eventhub.event_source source on source.id = event.event_source_id
|
||||||
|
join eventhub.data_package package on package.id = event.data_package_id
|
||||||
|
left join lateral (
|
||||||
|
select detail_type, attributes
|
||||||
|
from eventhub.event_detail detail
|
||||||
|
where detail.event_occurred_at = event.occurred_at
|
||||||
|
and detail.event_id = event.id
|
||||||
|
order by detail_type
|
||||||
|
limit 1
|
||||||
|
) detail on true
|
||||||
|
left join eventhub.driver driver on driver.id = event.driver_id
|
||||||
|
left join eventhub.driver_card driver_card on driver_card.id = event.driver_card_id
|
||||||
|
left join eventhub.vehicle vehicle on vehicle.id = event.vehicle_id
|
||||||
|
left join eventhub.vehicle_registration registration on registration.id = event.vehicle_registration_id
|
||||||
|
where package.tenant_key = ?
|
||||||
|
and source.provider_key = ?
|
||||||
|
"""
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Object> params = new ArrayList<>();
|
||||||
|
params.add(request.tenantKey());
|
||||||
|
params.add(providerKey);
|
||||||
|
|
||||||
|
if (sourceKinds != null && !sourceKinds.isEmpty()) {
|
||||||
|
sql.append(" and source.source_kind in (");
|
||||||
|
for (int i = 0; i < sourceKinds.size(); i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
sql.append(", ");
|
||||||
|
}
|
||||||
|
sql.append("?");
|
||||||
|
params.add(sourceKinds.get(i));
|
||||||
|
}
|
||||||
|
sql.append(")");
|
||||||
|
}
|
||||||
|
if (request.occurredFrom() != null) {
|
||||||
|
sql.append(" and event.occurred_at >= ?");
|
||||||
|
params.add(request.occurredFrom());
|
||||||
|
}
|
||||||
|
if (request.occurredTo() != null) {
|
||||||
|
sql.append(" and event.occurred_at <= ?");
|
||||||
|
params.add(request.occurredTo());
|
||||||
|
}
|
||||||
|
if (request.driverSourceEntityId() != null) {
|
||||||
|
sql.append(" and driver.source_driver_entity_id = ?");
|
||||||
|
params.add(request.driverSourceEntityId());
|
||||||
|
}
|
||||||
|
if (request.driverCardNumber() != null) {
|
||||||
|
sql.append(" and driver_card.card_number = ?");
|
||||||
|
params.add(request.driverCardNumber());
|
||||||
|
if (request.driverCardNation() != null) {
|
||||||
|
sql.append(" and driver_card.nation = ?");
|
||||||
|
params.add(request.driverCardNation());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (request.vehicleSourceEntityId() != null) {
|
||||||
|
sql.append(" and vehicle.source_vehicle_entity_id = ?");
|
||||||
|
params.add(request.vehicleSourceEntityId());
|
||||||
|
}
|
||||||
|
if (request.vin() != null) {
|
||||||
|
sql.append(" and vehicle.vin = ?");
|
||||||
|
params.add(request.vin());
|
||||||
|
}
|
||||||
|
if (request.registrationNumber() != null) {
|
||||||
|
sql.append(" and registration.registration_number = ?");
|
||||||
|
params.add(request.registrationNumber());
|
||||||
|
if (request.registrationNation() != null) {
|
||||||
|
sql.append(" and registration.nation = ?");
|
||||||
|
params.add(request.registrationNation());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sql.append(" order by event.occurred_at, event.event_domain, event.event_type, event.lifecycle, event.id");
|
||||||
|
|
||||||
|
return jdbcTemplate.query(
|
||||||
|
sql.toString(),
|
||||||
|
(rs, rowNum) -> mapEvent(rs),
|
||||||
|
params.toArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private EventHubEventDto mapEvent(ResultSet rs) throws SQLException {
|
||||||
|
DriverRefDto driverRef = driverRef(rs);
|
||||||
|
VehicleRefDto vehicleRef = vehicleRef(rs);
|
||||||
|
if ((driverRef == null || !driverRef.hasAnyReference()) && (vehicleRef == null || !vehicleRef.hasAnyReference())) {
|
||||||
|
throw new IllegalStateException("Loaded event does not have any driver or vehicle reference.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new EventHubEventDto(
|
||||||
|
uuid(rs, "id"),
|
||||||
|
rs.getString("external_source_event_id"),
|
||||||
|
driverRef,
|
||||||
|
vehicleRef,
|
||||||
|
rs.getObject("occurred_at", OffsetDateTime.class),
|
||||||
|
rs.getObject("received_partner_at", OffsetDateTime.class),
|
||||||
|
rs.getObject("received_hub_at", OffsetDateTime.class),
|
||||||
|
enumValue(EventDomain.class, rs.getString("event_domain"), EventDomain.TELEMATICS_DATA),
|
||||||
|
enumValue(EventType.class, rs.getString("event_type"), EventType.UNKNOWN_EVENT),
|
||||||
|
enumValue(EventLifecycle.class, rs.getString("lifecycle"), EventLifecycle.SNAPSHOT),
|
||||||
|
longValue(rs, "odometer_m"),
|
||||||
|
point(rs),
|
||||||
|
eventDetails(rs),
|
||||||
|
sourcePackageRef(rs),
|
||||||
|
json(rs.getString("payload")),
|
||||||
|
rs.getBoolean("manual_entry"),
|
||||||
|
packageInfo(rs)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DriverRefDto driverRef(ResultSet rs) throws SQLException {
|
||||||
|
String sourceEntityId = firstNonBlank(
|
||||||
|
rs.getString("source_driver_entity_id"),
|
||||||
|
syntheticId("EVENTHUB_DRIVER", uuid(rs, "driver_id"))
|
||||||
|
);
|
||||||
|
String cardNumber = rs.getString("driver_card_number");
|
||||||
|
DriverCardRefDto driverCard = cardNumber == null
|
||||||
|
? null
|
||||||
|
: new DriverCardRefDto(rs.getString("driver_card_nation"), cardNumber);
|
||||||
|
DriverRefDto driverRef = new DriverRefDto(sourceEntityId, driverCard);
|
||||||
|
return driverRef.hasAnyReference() ? driverRef : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private VehicleRefDto vehicleRef(ResultSet rs) throws SQLException {
|
||||||
|
VehicleRegistrationRefDto registration = null;
|
||||||
|
String registrationNumber = rs.getString("vehicle_registration_number");
|
||||||
|
if (registrationNumber != null) {
|
||||||
|
registration = new VehicleRegistrationRefDto(rs.getString("vehicle_registration_nation"), registrationNumber);
|
||||||
|
}
|
||||||
|
VehicleRefDto vehicleRef = new VehicleRefDto(
|
||||||
|
firstNonBlank(
|
||||||
|
rs.getString("source_vehicle_entity_id"),
|
||||||
|
syntheticId("EVENTHUB_VEHICLE", uuid(rs, "vehicle_id"))
|
||||||
|
),
|
||||||
|
rs.getString("vin"),
|
||||||
|
firstNonBlank(
|
||||||
|
rs.getString("source_registration_entity_id"),
|
||||||
|
syntheticId("EVENTHUB_VEHICLE_REGISTRATION", uuid(rs, "vehicle_registration_id"))
|
||||||
|
),
|
||||||
|
registration
|
||||||
|
);
|
||||||
|
return vehicleRef.hasAnyReference() ? vehicleRef : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EventDetailsDto eventDetails(ResultSet rs) throws SQLException {
|
||||||
|
String detailType = rs.getString("detail_type");
|
||||||
|
if (detailType == null || detailType.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new EventDetailsDto(detailType, json(rs.getString("attributes")));
|
||||||
|
}
|
||||||
|
|
||||||
|
private SourcePackageRefDto sourcePackageRef(ResultSet rs) throws SQLException {
|
||||||
|
String packageKind = rs.getString("package_source_package_kind");
|
||||||
|
String sourcePackageId = firstNonBlank(rs.getString("source_package_id"), null);
|
||||||
|
String sourceEntityId = firstNonBlank(rs.getString("source_package_entity_id"), null);
|
||||||
|
OffsetDateTime periodFrom = rs.getObject("source_package_period_from", OffsetDateTime.class);
|
||||||
|
OffsetDateTime periodTo = rs.getObject("source_package_period_to", OffsetDateTime.class);
|
||||||
|
OffsetDateTime importedAt = rs.getObject("source_package_imported_at", OffsetDateTime.class);
|
||||||
|
SourcePackageRefDto ref = new SourcePackageRefDto(
|
||||||
|
packageKind,
|
||||||
|
sourcePackageId,
|
||||||
|
sourceEntityId,
|
||||||
|
periodFrom,
|
||||||
|
periodTo,
|
||||||
|
importedAt
|
||||||
|
);
|
||||||
|
return ref.hasAnyReference() ? ref : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EventHubPackageRequest packageInfo(ResultSet rs) throws SQLException {
|
||||||
|
EventSourceDto eventSource = new EventSourceDto(
|
||||||
|
rs.getString("provider_key"),
|
||||||
|
rs.getString("source_kind"),
|
||||||
|
rs.getString("source_key"),
|
||||||
|
rs.getString("source_instance_key"),
|
||||||
|
rs.getString("tenant_provider_setting_key"),
|
||||||
|
rs.getString("external_fleet_key")
|
||||||
|
);
|
||||||
|
SourceGroupRefDto sourceGroup = sourceGroup(rs);
|
||||||
|
ImportScopeDto importScope = importScope(rs);
|
||||||
|
String eventFamily = firstNonBlank(rs.getString("event_family"), rs.getString("event_domain"));
|
||||||
|
LocalDate businessDate = rs.getObject("business_date", LocalDate.class);
|
||||||
|
String externalPackageId = firstNonBlank(rs.getString("external_package_id"), rs.getString("data_package_id"));
|
||||||
|
return new EventHubPackageRequest(
|
||||||
|
rs.getString("tenant_key"),
|
||||||
|
eventSource,
|
||||||
|
sourceGroup,
|
||||||
|
importScope,
|
||||||
|
eventFamily,
|
||||||
|
businessDate,
|
||||||
|
externalPackageId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SourceGroupRefDto sourceGroup(ResultSet rs) throws SQLException {
|
||||||
|
String groupType = rs.getString("source_group_type");
|
||||||
|
String sourceEntityId = rs.getString("source_group_entity_id");
|
||||||
|
String code = rs.getString("source_group_code");
|
||||||
|
String name = rs.getString("source_group_name");
|
||||||
|
if (groupType == null && sourceEntityId == null && code == null && name == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new SourceGroupRefDto(
|
||||||
|
enumValue(SourceGroupType.class, groupType, null),
|
||||||
|
sourceEntityId,
|
||||||
|
code,
|
||||||
|
name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImportScopeDto importScope(ResultSet rs) throws SQLException {
|
||||||
|
SourceGroupRefDto rootOrganisation = null;
|
||||||
|
String rootEntityId = rs.getString("root_source_org_entity_id");
|
||||||
|
String rootCode = rs.getString("root_source_org_code");
|
||||||
|
String rootName = rs.getString("root_source_org_name");
|
||||||
|
if (rootEntityId != null || rootCode != null || rootName != null) {
|
||||||
|
rootOrganisation = new SourceGroupRefDto(SourceGroupType.ORGANISATION, rootEntityId, rootCode, rootName);
|
||||||
|
}
|
||||||
|
return new ImportScopeDto(
|
||||||
|
enumValue(ImportScopeType.class, rs.getString("import_scope_type"), ImportScopeType.TENANT_ALL),
|
||||||
|
rootOrganisation,
|
||||||
|
rs.getBoolean("include_children"),
|
||||||
|
rs.getObject("package_occurred_from", OffsetDateTime.class),
|
||||||
|
rs.getObject("package_occurred_to", OffsetDateTime.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private GeoPointDto point(ResultSet rs) throws SQLException {
|
||||||
|
BigDecimal latitude = rs.getBigDecimal("latitude");
|
||||||
|
BigDecimal longitude = rs.getBigDecimal("longitude");
|
||||||
|
return latitude == null || longitude == null ? null : new GeoPointDto(latitude, longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
private UUID uuid(ResultSet rs, String column) throws SQLException {
|
||||||
|
return (UUID) rs.getObject(column);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long longValue(ResultSet rs, String column) throws SQLException {
|
||||||
|
Object value = rs.getObject(column);
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (value instanceof Number number) {
|
||||||
|
return number.longValue();
|
||||||
|
}
|
||||||
|
return Long.parseLong(value.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonNode json(String value) {
|
||||||
|
try {
|
||||||
|
return value == null || value.isBlank()
|
||||||
|
? objectMapper.createObjectNode()
|
||||||
|
: objectMapper.readTree(value);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new IllegalArgumentException("Failed to parse JSON column.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String syntheticId(String prefix, UUID id) {
|
||||||
|
return id == null ? null : prefix + ":" + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String firstNonBlank(String first, String second) {
|
||||||
|
if (first != null && !first.isBlank()) {
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
return second != null && !second.isBlank() ? second : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T extends Enum<T>> T enumValue(Class<T> type, String value, T fallback) {
|
||||||
|
if (value == null || value.isBlank()) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
String normalized = value.trim().toUpperCase(Locale.ROOT).replace('-', '_').replace(' ', '_');
|
||||||
|
try {
|
||||||
|
return Enum.valueOf(type, normalized);
|
||||||
|
} catch (IllegalArgumentException ignored) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,149 @@
|
||||||
|
package at.procon.eventhub.processing.model;
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public record UnifiedDriverEventsRequest(
|
||||||
|
UnifiedEventSourceFamily sourceFamily,
|
||||||
|
UUID sessionId,
|
||||||
|
String driverKey,
|
||||||
|
String tenantKey,
|
||||||
|
String driverSourceEntityId,
|
||||||
|
String driverCardNation,
|
||||||
|
String driverCardNumber,
|
||||||
|
String vehicleSourceEntityId,
|
||||||
|
String vin,
|
||||||
|
String registrationNation,
|
||||||
|
String registrationNumber,
|
||||||
|
OffsetDateTime occurredFrom,
|
||||||
|
OffsetDateTime occurredTo
|
||||||
|
) {
|
||||||
|
public UnifiedDriverEventsRequest {
|
||||||
|
Objects.requireNonNull(sourceFamily, "sourceFamily must not be null");
|
||||||
|
driverKey = normalize(driverKey);
|
||||||
|
tenantKey = normalize(tenantKey);
|
||||||
|
driverSourceEntityId = normalize(driverSourceEntityId);
|
||||||
|
driverCardNation = normalizeUpper(driverCardNation);
|
||||||
|
driverCardNumber = normalize(driverCardNumber);
|
||||||
|
vehicleSourceEntityId = normalize(vehicleSourceEntityId);
|
||||||
|
vin = normalizeUpper(vin);
|
||||||
|
registrationNation = normalizeUpper(registrationNation);
|
||||||
|
registrationNumber = normalize(registrationNumber);
|
||||||
|
if (occurredFrom != null && occurredTo != null && occurredTo.isBefore(occurredFrom)) {
|
||||||
|
throw new IllegalArgumentException("occurredTo must not be before occurredFrom");
|
||||||
|
}
|
||||||
|
if (sourceFamily == UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION) {
|
||||||
|
Objects.requireNonNull(sessionId, "sessionId must not be null");
|
||||||
|
if (driverKey == null) {
|
||||||
|
throw new IllegalArgumentException("driverKey must not be blank");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (tenantKey == null) {
|
||||||
|
throw new IllegalArgumentException("tenantKey must not be blank");
|
||||||
|
}
|
||||||
|
if (!hasDriverSelector(driverSourceEntityId, driverCardNumber)
|
||||||
|
&& !hasVehicleSelector(vehicleSourceEntityId, vin, registrationNumber)) {
|
||||||
|
throw new IllegalArgumentException("At least one driver or vehicle selector must be provided.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UnifiedDriverEventsRequest forTachographFileSession(
|
||||||
|
UUID sessionId,
|
||||||
|
String driverKey,
|
||||||
|
OffsetDateTime occurredFrom,
|
||||||
|
OffsetDateTime occurredTo
|
||||||
|
) {
|
||||||
|
return new UnifiedDriverEventsRequest(
|
||||||
|
UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION,
|
||||||
|
sessionId,
|
||||||
|
driverKey,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
occurredFrom,
|
||||||
|
occurredTo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UnifiedDriverEventsRequest forTachographDbDriver(
|
||||||
|
String tenantKey,
|
||||||
|
String driverSourceEntityId,
|
||||||
|
String driverCardNation,
|
||||||
|
String driverCardNumber,
|
||||||
|
OffsetDateTime occurredFrom,
|
||||||
|
OffsetDateTime occurredTo
|
||||||
|
) {
|
||||||
|
return new UnifiedDriverEventsRequest(
|
||||||
|
UnifiedEventSourceFamily.TACHOGRAPH_DB,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
tenantKey,
|
||||||
|
driverSourceEntityId,
|
||||||
|
driverCardNation,
|
||||||
|
driverCardNumber,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
occurredFrom,
|
||||||
|
occurredTo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UnifiedDriverEventsRequest forYellowFoxDbVehicle(
|
||||||
|
String tenantKey,
|
||||||
|
String vehicleSourceEntityId,
|
||||||
|
String vin,
|
||||||
|
String registrationNation,
|
||||||
|
String registrationNumber,
|
||||||
|
OffsetDateTime occurredFrom,
|
||||||
|
OffsetDateTime occurredTo
|
||||||
|
) {
|
||||||
|
return new UnifiedDriverEventsRequest(
|
||||||
|
UnifiedEventSourceFamily.YELLOWFOX_DB,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
tenantKey,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
vehicleSourceEntityId,
|
||||||
|
vin,
|
||||||
|
registrationNation,
|
||||||
|
registrationNumber,
|
||||||
|
occurredFrom,
|
||||||
|
occurredTo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasDriverSelector() {
|
||||||
|
return hasDriverSelector(driverSourceEntityId, driverCardNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasVehicleSelector() {
|
||||||
|
return hasVehicleSelector(vehicleSourceEntityId, vin, registrationNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasDriverSelector(String driverSourceEntityId, String driverCardNumber) {
|
||||||
|
return driverSourceEntityId != null || driverCardNumber != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasVehicleSelector(String vehicleSourceEntityId, String vin, String registrationNumber) {
|
||||||
|
return vehicleSourceEntityId != null || vin != null || registrationNumber != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalize(String value) {
|
||||||
|
return value == null || value.isBlank() ? null : value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalizeUpper(String value) {
|
||||||
|
return value == null || value.isBlank() ? null : value.trim().toUpperCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package at.procon.eventhub.processing.model;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public record UnifiedDriverTimelineRequest(
|
||||||
|
UnifiedEventSourceFamily sourceFamily,
|
||||||
|
UUID sessionId,
|
||||||
|
String driverKey
|
||||||
|
) {
|
||||||
|
public UnifiedDriverTimelineRequest {
|
||||||
|
Objects.requireNonNull(sourceFamily, "sourceFamily must not be null");
|
||||||
|
Objects.requireNonNull(sessionId, "sessionId must not be null");
|
||||||
|
if (driverKey == null || driverKey.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("driverKey must not be blank");
|
||||||
|
}
|
||||||
|
driverKey = driverKey.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UnifiedDriverTimelineRequest forTachographFileSession(
|
||||||
|
UUID sessionId,
|
||||||
|
String driverKey
|
||||||
|
) {
|
||||||
|
return new UnifiedDriverTimelineRequest(
|
||||||
|
UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION,
|
||||||
|
sessionId,
|
||||||
|
driverKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package at.procon.eventhub.processing.model;
|
||||||
|
|
||||||
|
public enum UnifiedEventSourceFamily {
|
||||||
|
TACHOGRAPH_FILE_SESSION,
|
||||||
|
TACHOGRAPH_DB,
|
||||||
|
YELLOWFOX_DB
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package at.procon.eventhub.processing.service;
|
||||||
|
|
||||||
|
import at.procon.eventhub.dto.EventHubEventDto;
|
||||||
|
import at.procon.eventhub.persistence.EventHubEventReadRepository;
|
||||||
|
import at.procon.eventhub.processing.model.UnifiedDriverEventsRequest;
|
||||||
|
import at.procon.eventhub.processing.model.UnifiedEventSourceFamily;
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class TachographDbUnifiedDriverEventSource implements UnifiedDriverEventSource {
|
||||||
|
|
||||||
|
private static final List<String> SOURCE_KINDS = List.of("DRIVER_CARD", "VEHICLE_UNIT");
|
||||||
|
|
||||||
|
private final EventHubEventReadRepository repository;
|
||||||
|
|
||||||
|
public TachographDbUnifiedDriverEventSource(EventHubEventReadRepository repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(UnifiedDriverEventsRequest request) {
|
||||||
|
return request.sourceFamily() == UnifiedEventSourceFamily.TACHOGRAPH_DB;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<EventHubEventDto> loadDriverEvents(UnifiedDriverEventsRequest request) {
|
||||||
|
return repository.findEvents(request, "TACHOGRAPH", SOURCE_KINDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
package at.procon.eventhub.processing.service;
|
||||||
|
|
||||||
|
import at.procon.eventhub.dto.EventHubEventDto;
|
||||||
|
import at.procon.eventhub.processing.model.UnifiedDriverEventsRequest;
|
||||||
|
import at.procon.eventhub.processing.model.UnifiedEventSourceFamily;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.DriverNotFoundInSessionException;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.DriverTimelineEventBuilder;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.TachographFileSessionNotFoundException;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.TachographFileSessionRepository;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class TachographFileSessionUnifiedDriverEventSource implements UnifiedDriverEventSource {
|
||||||
|
|
||||||
|
private final TachographFileSessionRepository repository;
|
||||||
|
private final DriverTimelineEventBuilder eventBuilder;
|
||||||
|
|
||||||
|
public TachographFileSessionUnifiedDriverEventSource(
|
||||||
|
TachographFileSessionRepository repository,
|
||||||
|
DriverTimelineEventBuilder eventBuilder
|
||||||
|
) {
|
||||||
|
this.repository = repository;
|
||||||
|
this.eventBuilder = eventBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(UnifiedDriverEventsRequest request) {
|
||||||
|
return request.sourceFamily() == UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<EventHubEventDto> loadDriverEvents(UnifiedDriverEventsRequest request) {
|
||||||
|
TachographFileSession session = repository.find(request.sessionId())
|
||||||
|
.orElseThrow(() -> new TachographFileSessionNotFoundException(request.sessionId()));
|
||||||
|
DriverExtractionSession driver = session.driversByKey().get(request.driverKey());
|
||||||
|
if (driver == null) {
|
||||||
|
throw new DriverNotFoundInSessionException(request.sessionId(), request.driverKey());
|
||||||
|
}
|
||||||
|
return eventBuilder.buildEvents(session, driver).stream()
|
||||||
|
.filter(event -> withinWindow(event.occurredAt(), request.occurredFrom(), request.occurredTo()))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean withinWindow(
|
||||||
|
OffsetDateTime occurredAt,
|
||||||
|
OffsetDateTime occurredFrom,
|
||||||
|
OffsetDateTime occurredTo
|
||||||
|
) {
|
||||||
|
if (occurredAt == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (occurredFrom != null && occurredAt.isBefore(occurredFrom)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return occurredTo == null || !occurredAt.isAfter(occurredTo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package at.procon.eventhub.processing.service;
|
||||||
|
|
||||||
|
import at.procon.eventhub.processing.model.UnifiedDriverTimelineRequest;
|
||||||
|
import at.procon.eventhub.processing.model.UnifiedEventSourceFamily;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.DriverNotFoundInSessionException;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.EventBackedDriverTimelineBuilder;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.TachographFileSessionNotFoundException;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.TachographFileSessionRepository;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class TachographFileSessionUnifiedDriverTimelineSource implements UnifiedDriverTimelineSource {
|
||||||
|
|
||||||
|
private final TachographFileSessionRepository repository;
|
||||||
|
private final EventBackedDriverTimelineBuilder eventBackedDriverTimelineBuilder;
|
||||||
|
|
||||||
|
public TachographFileSessionUnifiedDriverTimelineSource(
|
||||||
|
TachographFileSessionRepository repository,
|
||||||
|
EventBackedDriverTimelineBuilder eventBackedDriverTimelineBuilder
|
||||||
|
) {
|
||||||
|
this.repository = repository;
|
||||||
|
this.eventBackedDriverTimelineBuilder = eventBackedDriverTimelineBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(UnifiedDriverTimelineRequest request) {
|
||||||
|
return request.sourceFamily() == UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResolvedDriverTimeline loadDriverTimeline(UnifiedDriverTimelineRequest request) {
|
||||||
|
TachographFileSession session = repository.find(request.sessionId())
|
||||||
|
.orElseThrow(() -> new TachographFileSessionNotFoundException(request.sessionId()));
|
||||||
|
DriverExtractionSession driver = session.driversByKey().get(request.driverKey());
|
||||||
|
if (driver == null) {
|
||||||
|
throw new DriverNotFoundInSessionException(request.sessionId(), request.driverKey());
|
||||||
|
}
|
||||||
|
return eventBackedDriverTimelineBuilder.build(session, driver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package at.procon.eventhub.processing.service;
|
||||||
|
|
||||||
|
import at.procon.eventhub.dto.EventHubEventDto;
|
||||||
|
import at.procon.eventhub.processing.model.UnifiedDriverEventsRequest;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface UnifiedDriverEventSource {
|
||||||
|
|
||||||
|
boolean supports(UnifiedDriverEventsRequest request);
|
||||||
|
|
||||||
|
List<EventHubEventDto> loadDriverEvents(UnifiedDriverEventsRequest request);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package at.procon.eventhub.processing.service;
|
||||||
|
|
||||||
|
import at.procon.eventhub.dto.EventHubEventDto;
|
||||||
|
import at.procon.eventhub.processing.model.UnifiedDriverEventsRequest;
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class UnifiedDriverEventSourceService {
|
||||||
|
|
||||||
|
private final List<UnifiedDriverEventSource> eventSources;
|
||||||
|
|
||||||
|
public UnifiedDriverEventSourceService(List<UnifiedDriverEventSource> eventSources) {
|
||||||
|
this.eventSources = List.copyOf(eventSources);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<EventHubEventDto> loadDriverEvents(UnifiedDriverEventsRequest request) {
|
||||||
|
return eventSources.stream()
|
||||||
|
.filter(source -> source.supports(request))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> unsupportedSource(request.sourceFamily().name()))
|
||||||
|
.loadDriverEvents(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IllegalArgumentException unsupportedSource(String sourceFamily) {
|
||||||
|
return new IllegalArgumentException("No unified driver event source is registered for source family " + sourceFamily + ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package at.procon.eventhub.processing.service;
|
||||||
|
|
||||||
|
import at.procon.eventhub.processing.model.UnifiedDriverTimelineRequest;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class UnifiedDriverTimelineService {
|
||||||
|
|
||||||
|
private final List<UnifiedDriverTimelineSource> timelineSources;
|
||||||
|
|
||||||
|
public UnifiedDriverTimelineService(List<UnifiedDriverTimelineSource> timelineSources) {
|
||||||
|
this.timelineSources = List.copyOf(timelineSources);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResolvedDriverTimeline loadDriverTimeline(UnifiedDriverTimelineRequest request) {
|
||||||
|
return timelineSources.stream()
|
||||||
|
.filter(source -> source.supports(request))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> unsupportedSource(request.sourceFamily().name()))
|
||||||
|
.loadDriverTimeline(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IllegalArgumentException unsupportedSource(String sourceFamily) {
|
||||||
|
return new IllegalArgumentException("No unified driver timeline source is registered for source family " + sourceFamily + ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package at.procon.eventhub.processing.service;
|
||||||
|
|
||||||
|
import at.procon.eventhub.processing.model.UnifiedDriverTimelineRequest;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
|
||||||
|
|
||||||
|
public interface UnifiedDriverTimelineSource {
|
||||||
|
|
||||||
|
boolean supports(UnifiedDriverTimelineRequest request);
|
||||||
|
|
||||||
|
ResolvedDriverTimeline loadDriverTimeline(UnifiedDriverTimelineRequest request);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package at.procon.eventhub.processing.service;
|
||||||
|
|
||||||
|
import at.procon.eventhub.dto.EventHubEventDto;
|
||||||
|
import at.procon.eventhub.persistence.EventHubEventReadRepository;
|
||||||
|
import at.procon.eventhub.processing.model.UnifiedDriverEventsRequest;
|
||||||
|
import at.procon.eventhub.processing.model.UnifiedEventSourceFamily;
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class YellowFoxDbUnifiedDriverEventSource implements UnifiedDriverEventSource {
|
||||||
|
|
||||||
|
private final EventHubEventReadRepository repository;
|
||||||
|
|
||||||
|
public YellowFoxDbUnifiedDriverEventSource(EventHubEventReadRepository repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(UnifiedDriverEventsRequest request) {
|
||||||
|
return request.sourceFamily() == UnifiedEventSourceFamily.YELLOWFOX_DB;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<EventHubEventDto> loadDriverEvents(UnifiedDriverEventsRequest request) {
|
||||||
|
return repository.findEvents(request, "YELLOWFOX", List.of("TELEMATICS_PLATFORM"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
package at.procon.eventhub.processing.model;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class UnifiedDriverEventsRequestTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildsFileSessionRequest() {
|
||||||
|
UUID sessionId = UUID.randomUUID();
|
||||||
|
|
||||||
|
UnifiedDriverEventsRequest request = UnifiedDriverEventsRequest.forTachographFileSession(
|
||||||
|
sessionId,
|
||||||
|
" 12:123 ",
|
||||||
|
OffsetDateTime.parse("2026-05-01T00:00:00Z"),
|
||||||
|
OffsetDateTime.parse("2026-05-02T00:00:00Z")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(request.sourceFamily()).isEqualTo(UnifiedEventSourceFamily.TACHOGRAPH_FILE_SESSION);
|
||||||
|
assertThat(request.sessionId()).isEqualTo(sessionId);
|
||||||
|
assertThat(request.driverKey()).isEqualTo("12:123");
|
||||||
|
assertThat(request.tenantKey()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildsTachographDbDriverRequest() {
|
||||||
|
UnifiedDriverEventsRequest request = UnifiedDriverEventsRequest.forTachographDbDriver(
|
||||||
|
" default ",
|
||||||
|
" DRIVER:42 ",
|
||||||
|
"at",
|
||||||
|
" 123 ",
|
||||||
|
OffsetDateTime.parse("2026-05-01T00:00:00Z"),
|
||||||
|
OffsetDateTime.parse("2026-05-02T00:00:00Z")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(request.sourceFamily()).isEqualTo(UnifiedEventSourceFamily.TACHOGRAPH_DB);
|
||||||
|
assertThat(request.tenantKey()).isEqualTo("default");
|
||||||
|
assertThat(request.driverSourceEntityId()).isEqualTo("DRIVER:42");
|
||||||
|
assertThat(request.driverCardNation()).isEqualTo("AT");
|
||||||
|
assertThat(request.driverCardNumber()).isEqualTo("123");
|
||||||
|
assertThat(request.hasDriverSelector()).isTrue();
|
||||||
|
assertThat(request.hasVehicleSelector()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildsYellowFoxVehicleRequest() {
|
||||||
|
UnifiedDriverEventsRequest request = UnifiedDriverEventsRequest.forYellowFoxDbVehicle(
|
||||||
|
"default",
|
||||||
|
"VEHICLE:99",
|
||||||
|
"wdb123",
|
||||||
|
"de",
|
||||||
|
"W-123AB",
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(request.sourceFamily()).isEqualTo(UnifiedEventSourceFamily.YELLOWFOX_DB);
|
||||||
|
assertThat(request.vehicleSourceEntityId()).isEqualTo("VEHICLE:99");
|
||||||
|
assertThat(request.vin()).isEqualTo("WDB123");
|
||||||
|
assertThat(request.registrationNation()).isEqualTo("DE");
|
||||||
|
assertThat(request.registrationNumber()).isEqualTo("W-123AB");
|
||||||
|
assertThat(request.hasVehicleSelector()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsDbRequestWithoutSubjectSelector() {
|
||||||
|
assertThatThrownBy(() -> new UnifiedDriverEventsRequest(
|
||||||
|
UnifiedEventSourceFamily.TACHOGRAPH_DB,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"default",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)).isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessageContaining("At least one driver or vehicle selector");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,165 @@
|
||||||
|
package at.procon.eventhub.processing.service;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import at.procon.eventhub.config.EventHubProperties;
|
||||||
|
import at.procon.eventhub.dto.EventDomain;
|
||||||
|
import at.procon.eventhub.dto.EventLifecycle;
|
||||||
|
import at.procon.eventhub.processing.model.UnifiedDriverEventsRequest;
|
||||||
|
import at.procon.eventhub.service.EventDetailsFactory;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedCardActivityInterval;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedCardVehicleUsageInterval;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedDriver;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedDriverCard;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedSupportEvent;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedVehicle;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedVehicleRegistration;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractionStats;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographFileSessionMetadata;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.DriverKeyFactory;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.DriverTimelineBuilder;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.InMemoryTachographFileSessionRepository;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.IntervalBackedDriverTimelineEventBuilder;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.TachographFileSessionRepository;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.VehicleKeyFactory;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class UnifiedDriverEventSourceServiceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void loadsNormalizedFileSessionEventsThroughUnifiedService() {
|
||||||
|
EventHubProperties properties = new EventHubProperties();
|
||||||
|
TachographFileSessionRepository repository = new InMemoryTachographFileSessionRepository(properties);
|
||||||
|
DriverTimelineBuilder timelineBuilder = new DriverTimelineBuilder();
|
||||||
|
UnifiedDriverEventSourceService service = new UnifiedDriverEventSourceService(List.of(
|
||||||
|
new TachographFileSessionUnifiedDriverEventSource(
|
||||||
|
repository,
|
||||||
|
new IntervalBackedDriverTimelineEventBuilder(
|
||||||
|
timelineBuilder,
|
||||||
|
new DriverKeyFactory(),
|
||||||
|
new VehicleKeyFactory(),
|
||||||
|
new EventDetailsFactory(new ObjectMapper())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
));
|
||||||
|
|
||||||
|
DriverExtractionSession driver = driver();
|
||||||
|
TachographFileSession session = session(driver);
|
||||||
|
repository.save(session);
|
||||||
|
|
||||||
|
List<at.procon.eventhub.dto.EventHubEventDto> events = service.loadDriverEvents(
|
||||||
|
UnifiedDriverEventsRequest.forTachographFileSession(
|
||||||
|
session.sessionId(),
|
||||||
|
driver.driverKey(),
|
||||||
|
OffsetDateTime.parse("2026-05-01T08:30:00Z"),
|
||||||
|
OffsetDateTime.parse("2026-05-01T09:00:00Z")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(events).hasSize(3);
|
||||||
|
assertThat(events).extracting(event -> event.occurredAt())
|
||||||
|
.containsExactly(
|
||||||
|
OffsetDateTime.parse("2026-05-01T08:30:00Z"),
|
||||||
|
OffsetDateTime.parse("2026-05-01T08:45:00Z"),
|
||||||
|
OffsetDateTime.parse("2026-05-01T09:00:00Z")
|
||||||
|
);
|
||||||
|
assertThat(events).extracting(event -> event.eventDomain())
|
||||||
|
.containsExactly(
|
||||||
|
EventDomain.DRIVER_ACTIVITY,
|
||||||
|
EventDomain.POSITION,
|
||||||
|
EventDomain.DRIVER_ACTIVITY
|
||||||
|
);
|
||||||
|
assertThat(events.get(0).lifecycle()).isEqualTo(EventLifecycle.START);
|
||||||
|
assertThat(events.get(2).lifecycle()).isEqualTo(EventLifecycle.END);
|
||||||
|
assertThat(events.get(1).eventDetails().type()).isEqualTo("POSITION");
|
||||||
|
}
|
||||||
|
|
||||||
|
private DriverExtractionSession driver() {
|
||||||
|
return new DriverExtractionSession(
|
||||||
|
"12:123",
|
||||||
|
new ExtractedDriver("12:123", "DRV:12:123", "Doe", "Jane", null, null, null, null, null),
|
||||||
|
new ExtractedDriverCard("CARD:12:123", "12", "123", null, null, null, null),
|
||||||
|
List.of(new ExtractedVehicleRegistration("12:REG-1", "VR:12:REG-1", "12", "REG-1")),
|
||||||
|
List.of(new ExtractedVehicle("VIN-1", "VIN:VIN-1", "VIN-1")),
|
||||||
|
List.of(new ExtractedCardVehicleUsageInterval(
|
||||||
|
"CVU-1",
|
||||||
|
OffsetDateTime.parse("2026-05-01T08:00:00Z"),
|
||||||
|
OffsetDateTime.parse("2026-05-01T10:00:00Z"),
|
||||||
|
100L,
|
||||||
|
200L,
|
||||||
|
"12:REG-1",
|
||||||
|
"VIN-1",
|
||||||
|
"vu-1"
|
||||||
|
)),
|
||||||
|
List.of(new ExtractedCardActivityInterval(
|
||||||
|
"ACT-1",
|
||||||
|
OffsetDateTime.parse("2026-05-01T08:30:00Z"),
|
||||||
|
OffsetDateTime.parse("2026-05-01T09:00:00Z"),
|
||||||
|
"DRIVE",
|
||||||
|
"DRIVER",
|
||||||
|
"INSERTED",
|
||||||
|
"SINGLE",
|
||||||
|
"12:REG-1",
|
||||||
|
"VIN-1",
|
||||||
|
"a"
|
||||||
|
)),
|
||||||
|
List.of(new ExtractedSupportEvent(
|
||||||
|
"SUP-1",
|
||||||
|
OffsetDateTime.parse("2026-05-01T08:45:00Z"),
|
||||||
|
"POSITION",
|
||||||
|
"POSITION_RECORDED",
|
||||||
|
"SNAPSHOT",
|
||||||
|
"DRIVER",
|
||||||
|
"12:REG-1",
|
||||||
|
"VIN-1",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
BigDecimal.valueOf(48.2082),
|
||||||
|
BigDecimal.valueOf(16.3738),
|
||||||
|
"AUTHENTIC",
|
||||||
|
150L,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"raw-path"
|
||||||
|
)),
|
||||||
|
List.of()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TachographFileSession session(DriverExtractionSession driver) {
|
||||||
|
return new TachographFileSession(
|
||||||
|
UUID.randomUUID(),
|
||||||
|
new TachographFileSessionMetadata(
|
||||||
|
"default",
|
||||||
|
"legalrequirements-drivercard",
|
||||||
|
"sample",
|
||||||
|
"sample.ddd",
|
||||||
|
"a",
|
||||||
|
2,
|
||||||
|
"42",
|
||||||
|
"b",
|
||||||
|
true,
|
||||||
|
null
|
||||||
|
),
|
||||||
|
Map.of(driver.driverKey(), driver),
|
||||||
|
new ExtractionStats(1, 1, 1, 1, 1, 0),
|
||||||
|
List.of(),
|
||||||
|
Instant.now(),
|
||||||
|
Instant.now().plus(4, ChronoUnit.HOURS)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
package at.procon.eventhub.processing.service;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import at.procon.eventhub.config.EventHubProperties;
|
||||||
|
import at.procon.eventhub.processing.model.UnifiedDriverTimelineRequest;
|
||||||
|
import at.procon.eventhub.service.EventDetailsFactory;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedCardActivityInterval;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedCardVehicleUsageInterval;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedDriver;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedDriverCard;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedSupportEvent;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedVehicle;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractedVehicleRegistration;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ExtractionStats;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
|
||||||
|
import at.procon.eventhub.tachographfilesession.model.TachographFileSessionMetadata;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.DriverKeyFactory;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.EventBackedDriverTimelineBuilder;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.InMemoryTachographFileSessionRepository;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.IntervalBackedDriverTimelineEventBuilder;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.TachographFileSessionRepository;
|
||||||
|
import at.procon.eventhub.tachographfilesession.service.VehicleKeyFactory;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class UnifiedDriverTimelineServiceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void reconstructsTimelineThroughUnifiedService() {
|
||||||
|
EventHubProperties properties = new EventHubProperties();
|
||||||
|
TachographFileSessionRepository repository = new InMemoryTachographFileSessionRepository(properties);
|
||||||
|
EventBackedDriverTimelineBuilder eventBackedBuilder = new EventBackedDriverTimelineBuilder(
|
||||||
|
new IntervalBackedDriverTimelineEventBuilder(
|
||||||
|
new at.procon.eventhub.tachographfilesession.service.DriverTimelineBuilder(),
|
||||||
|
new DriverKeyFactory(),
|
||||||
|
new VehicleKeyFactory(),
|
||||||
|
new EventDetailsFactory(new ObjectMapper())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
UnifiedDriverTimelineService service = new UnifiedDriverTimelineService(List.of(
|
||||||
|
new TachographFileSessionUnifiedDriverTimelineSource(repository, eventBackedBuilder)
|
||||||
|
));
|
||||||
|
|
||||||
|
DriverExtractionSession driver = driver();
|
||||||
|
TachographFileSession session = session(driver);
|
||||||
|
repository.save(session);
|
||||||
|
|
||||||
|
ResolvedDriverTimeline timeline = service.loadDriverTimeline(
|
||||||
|
UnifiedDriverTimelineRequest.forTachographFileSession(session.sessionId(), driver.driverKey())
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(timeline.sourceKind()).isEqualTo("DRIVER_CARD");
|
||||||
|
assertThat(timeline.loadedFrom()).isEqualTo(OffsetDateTime.parse("2026-05-01T08:00:00Z"));
|
||||||
|
assertThat(timeline.loadedTo()).isEqualTo(OffsetDateTime.parse("2026-05-01T10:00:00Z"));
|
||||||
|
assertThat(timeline.activityIntervals()).hasSize(1);
|
||||||
|
assertThat(timeline.vehicleUsageIntervals()).hasSize(1);
|
||||||
|
assertThat(timeline.supportEvents()).hasSize(1);
|
||||||
|
assertThat(timeline.activityIntervals().get(0).activityType()).isEqualTo("DRIVE");
|
||||||
|
assertThat(timeline.vehicleUsageIntervals().get(0).registrationKey()).isEqualTo("12:REG-1");
|
||||||
|
}
|
||||||
|
|
||||||
|
private DriverExtractionSession driver() {
|
||||||
|
return new DriverExtractionSession(
|
||||||
|
"12:123",
|
||||||
|
new ExtractedDriver("12:123", "DRV:12:123", "Doe", "Jane", null, null, null, null, null),
|
||||||
|
new ExtractedDriverCard("CARD:12:123", "12", "123", null, null, null, null),
|
||||||
|
List.of(new ExtractedVehicleRegistration("12:REG-1", "VR:12:REG-1", "12", "REG-1")),
|
||||||
|
List.of(new ExtractedVehicle("VIN-1", "VIN:VIN-1", "VIN-1")),
|
||||||
|
List.of(new ExtractedCardVehicleUsageInterval(
|
||||||
|
"CVU-1",
|
||||||
|
OffsetDateTime.parse("2026-05-01T08:00:00Z"),
|
||||||
|
OffsetDateTime.parse("2026-05-01T10:00:00Z"),
|
||||||
|
100L,
|
||||||
|
200L,
|
||||||
|
"12:REG-1",
|
||||||
|
"VIN-1",
|
||||||
|
"vu-1"
|
||||||
|
)),
|
||||||
|
List.of(new ExtractedCardActivityInterval(
|
||||||
|
"ACT-1",
|
||||||
|
OffsetDateTime.parse("2026-05-01T08:30:00Z"),
|
||||||
|
OffsetDateTime.parse("2026-05-01T09:00:00Z"),
|
||||||
|
"DRIVE",
|
||||||
|
"DRIVER",
|
||||||
|
"INSERTED",
|
||||||
|
"SINGLE",
|
||||||
|
"12:REG-1",
|
||||||
|
"VIN-1",
|
||||||
|
"a"
|
||||||
|
)),
|
||||||
|
List.of(new ExtractedSupportEvent(
|
||||||
|
"SUP-1",
|
||||||
|
OffsetDateTime.parse("2026-05-01T08:45:00Z"),
|
||||||
|
"POSITION",
|
||||||
|
"POSITION_RECORDED",
|
||||||
|
"SNAPSHOT",
|
||||||
|
"DRIVER",
|
||||||
|
"12:REG-1",
|
||||||
|
"VIN-1",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
BigDecimal.valueOf(48.2082),
|
||||||
|
BigDecimal.valueOf(16.3738),
|
||||||
|
"AUTHENTIC",
|
||||||
|
150L,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"raw-path"
|
||||||
|
)),
|
||||||
|
List.of()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TachographFileSession session(DriverExtractionSession driver) {
|
||||||
|
return new TachographFileSession(
|
||||||
|
UUID.randomUUID(),
|
||||||
|
new TachographFileSessionMetadata(
|
||||||
|
"default",
|
||||||
|
"legalrequirements-drivercard",
|
||||||
|
"sample",
|
||||||
|
"sample.ddd",
|
||||||
|
"a",
|
||||||
|
2,
|
||||||
|
"42",
|
||||||
|
"b",
|
||||||
|
true,
|
||||||
|
null
|
||||||
|
),
|
||||||
|
Map.of(driver.driverKey(), driver),
|
||||||
|
new ExtractionStats(1, 1, 1, 1, 1, 0),
|
||||||
|
List.of(),
|
||||||
|
Instant.now(),
|
||||||
|
Instant.now().plus(4, ChronoUnit.HOURS)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue