diff --git a/src/main/java/at/procon/eventhub/persistence/EventRepository.java b/src/main/java/at/procon/eventhub/persistence/EventRepository.java index cdfc3d4..a5bae89 100644 --- a/src/main/java/at/procon/eventhub/persistence/EventRepository.java +++ b/src/main/java/at/procon/eventhub/persistence/EventRepository.java @@ -4,7 +4,10 @@ import at.procon.eventhub.dto.DriverCardRefDto; import at.procon.eventhub.dto.DriverRefDto; import at.procon.eventhub.dto.EventHubEventDto; import at.procon.eventhub.dto.SourcePackageRefDto; +import at.procon.eventhub.dto.VehicleRefDto; +import at.procon.eventhub.dto.VehicleRegistrationRefDto; import at.procon.eventhub.persistence.VehicleIdentityRepository.ResolvedVehicleReference; +import at.procon.eventhub.persistence.VehicleIdentityRepository.ResolvedVehicleReferenceResolution; import at.procon.eventhub.service.EventAcquisitionRecordKeyService; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; @@ -51,10 +54,11 @@ public class EventRepository { */ public int batchInsert(UUID packageId, String tenantKey, int eventSourceId, List events) { Map entityIdCache = new HashMap<>(); + Map> vehicleRefCache = new HashMap<>(); List rows = new ArrayList<>(events.size()); for (EventHubEventDto event : events) { - ResolvedEntityRefs refs = resolveEntityRefs(tenantKey, eventSourceId, event, entityIdCache); + ResolvedEntityRefs refs = resolveEntityRefs(tenantKey, eventSourceId, event, entityIdCache, vehicleRefCache); rows.add(resolveEventImportRow(packageId, eventSourceId, event, refs)); } @@ -350,10 +354,11 @@ public class EventRepository { String tenantKey, int eventSourceId, EventHubEventDto event, - Map entityIdCache + Map entityIdCache, + Map> vehicleRefCache ) { UUID driverEntityId = resolveDriverEntityId(tenantKey, eventSourceId, event, entityIdCache); - ResolvedVehicleReference vehicleRef = resolveVehicleReference(tenantKey, eventSourceId, event); + ResolvedVehicleReference vehicleRef = resolveVehicleReference(tenantKey, eventSourceId, event, vehicleRefCache); UUID sourcePackageEntityId = resolveSourcePackageEntityId(tenantKey, eventSourceId, event, entityIdCache); return new ResolvedEntityRefs(driverEntityId, vehicleRef.vehicleId(), vehicleRef.vehicleRegistrationId(), sourcePackageEntityId); } @@ -399,14 +404,32 @@ public class EventRepository { private ResolvedVehicleReference resolveVehicleReference( String tenantKey, int eventSourceId, - EventHubEventDto event + EventHubEventDto event, + Map> vehicleRefCache ) { - return vehicleIdentityRepository.resolveOrCreateVehicleReference( + String cacheKey = vehicleRefCacheKey(event.vehicleRef()); + if (cacheKey != null) { + ResolvedVehicleReference cached = findCachedVehicleReference( + vehicleRefCache.get(cacheKey), + event.occurredAt() + ); + if (cached != null) { + return cached; + } + } + + ResolvedVehicleReferenceResolution resolved = vehicleIdentityRepository.resolveOrCreateVehicleReference( tenantKey, eventSourceId, event.vehicleRef(), event.occurredAt() ); + if (cacheKey != null) { + vehicleRefCache + .computeIfAbsent(cacheKey, ignored -> new ArrayList<>()) + .add(buildVehicleRefCacheEntry(event.vehicleRef(), event.occurredAt(), resolved)); + } + return resolved.reference(); } private UUID resolveSourcePackageEntityId( @@ -469,7 +492,7 @@ public class EventRepository { return null; } - String cacheKey = entityType + "|" + normalizedSourceEntityId + "|" + normalizeNullable(sourceExternalKey); + String cacheKey = entityType + "|" + normalizedSourceEntityId; UUID cached = entityIdCache.get(cacheKey); if (cached != null) { return cached; @@ -503,6 +526,70 @@ public class EventRepository { } } + private String vehicleRefCacheKey(VehicleRefDto vehicleRef) { + if (vehicleRef == null || !vehicleRef.hasAnyReference()) { + return null; + } + + String sourceVehicleEntityId = normalizeNullable(vehicleRef.sourceVehicleEntityId()); + String vin = normalizeNullable(vehicleRef.vin()); + String sourceRegistrationEntityId = normalizeNullable(vehicleRef.sourceRegistrationEntityId()); + VehicleRegistrationRefDto registration = vehicleRef.vehicleRegistration(); + String registrationNation = registration == null ? null : normalizeNullable(registration.nation()); + String registrationNumber = registration == null ? null : normalizeNullable(registration.number()); + + return String.join("|", + sourceVehicleEntityId == null ? "" : sourceVehicleEntityId, + vin == null ? "" : vin, + sourceRegistrationEntityId == null ? "" : sourceRegistrationEntityId, + registrationNation == null ? "" : registrationNation, + registrationNumber == null ? "" : registrationNumber + ); + } + + private ResolvedVehicleReference findCachedVehicleReference( + List cacheEntries, + OffsetDateTime occurredAt + ) { + if (cacheEntries == null || cacheEntries.isEmpty()) { + return null; + } + for (VehicleRefCacheEntry cacheEntry : cacheEntries) { + if (cacheEntry.matches(occurredAt)) { + return cacheEntry.reference(); + } + } + return null; + } + + private VehicleRefCacheEntry buildVehicleRefCacheEntry( + VehicleRefDto vehicleRef, + OffsetDateTime occurredAt, + ResolvedVehicleReferenceResolution resolved + ) { + boolean hasDirectVehicleIdentity = hasDirectVehicleIdentity(vehicleRef); + if (hasDirectVehicleIdentity) { + return new VehicleRefCacheEntry(resolved.reference(), null, null, null); + } + if (resolved.resolvedFromAssignment()) { + return new VehicleRefCacheEntry( + resolved.reference(), + resolved.assignmentValidFrom(), + resolved.assignmentValidTo(), + null + ); + } + return new VehicleRefCacheEntry(resolved.reference(), null, null, occurredAt); + } + + private boolean hasDirectVehicleIdentity(VehicleRefDto vehicleRef) { + if (vehicleRef == null) { + return false; + } + return normalizeNullable(vehicleRef.sourceVehicleEntityId()) != null + || normalizeNullable(vehicleRef.vin()) != null; + } + private String toJson(JsonNode value) { try { return objectMapper.writeValueAsString(value == null ? objectMapper.createObjectNode() : value); @@ -560,4 +647,23 @@ public class EventRepository { String attributesJson ) { } + + private record VehicleRefCacheEntry( + ResolvedVehicleReference reference, + OffsetDateTime validFrom, + OffsetDateTime validTo, + OffsetDateTime exactOccurredAt + ) { + private boolean matches(OffsetDateTime occurredAt) { + if (exactOccurredAt != null) { + return exactOccurredAt.equals(occurredAt); + } + if (validFrom == null && validTo == null) { + return true; + } + boolean fromMatches = validFrom == null || occurredAt == null || !occurredAt.isBefore(validFrom); + boolean toMatches = validTo == null || occurredAt == null || occurredAt.isBefore(validTo); + return fromMatches && toMatches; + } + } } diff --git a/src/main/java/at/procon/eventhub/persistence/VehicleIdentityRepository.java b/src/main/java/at/procon/eventhub/persistence/VehicleIdentityRepository.java index 0b3c524..671e0f0 100644 --- a/src/main/java/at/procon/eventhub/persistence/VehicleIdentityRepository.java +++ b/src/main/java/at/procon/eventhub/persistence/VehicleIdentityRepository.java @@ -24,14 +24,14 @@ public class VehicleIdentityRepository { this.objectMapper = objectMapper; } - public ResolvedVehicleReference resolveOrCreateVehicleReference( + public ResolvedVehicleReferenceResolution resolveOrCreateVehicleReference( String tenantKey, int eventSourceId, VehicleRefDto vehicleRef, OffsetDateTime occurredAt ) { if (vehicleRef == null || !vehicleRef.hasAnyReference()) { - return ResolvedVehicleReference.empty(); + return ResolvedVehicleReferenceResolution.empty(); } String normalizedTenantKey = normalizeRequired(tenantKey, "tenantKey"); @@ -68,8 +68,10 @@ public class VehicleIdentityRepository { sourceVehicleEntityId, vin ); + AssignedVehicleReference assignedVehicle = null; if (vehicleId == null && registrationId != null) { - vehicleId = resolveAssignedVehicleId(registrationId, occurredAt); + assignedVehicle = resolveAssignedVehicleReference(registrationId, occurredAt); + vehicleId = assignedVehicle == null ? null : assignedVehicle.vehicleId(); } if (vehicleId == null && (sourceVehicleEntityId != null || vin != null)) { vehicleId = createVehicle( @@ -86,7 +88,12 @@ public class VehicleIdentityRepository { if (registrationId != null) { touchRegistration(registrationId, sourceRegistrationEntityId, registrationNation, registrationNumber); } - return new ResolvedVehicleReference(vehicleId, registrationId); + return new ResolvedVehicleReferenceResolution( + new ResolvedVehicleReference(vehicleId, registrationId), + assignedVehicle != null, + assignedVehicle == null ? null : assignedVehicle.validFrom(), + assignedVehicle == null ? null : assignedVehicle.validTo() + ); } @Transactional @@ -431,10 +438,10 @@ public class VehicleIdentityRepository { ); } - private UUID resolveAssignedVehicleId(UUID registrationId, OffsetDateTime occurredAt) { + private AssignedVehicleReference resolveAssignedVehicleReference(UUID registrationId, OffsetDateTime occurredAt) { return jdbcTemplate.query( """ - select vehicle_id + select vehicle_id, valid_from, valid_to from eventhub.vehicle_registration_assignment where vehicle_registration_id = ? and (? is null or valid_from is null or valid_from <= ?) @@ -442,7 +449,13 @@ public class VehicleIdentityRepository { order by valid_from desc nulls last, updated_at desc limit 1 """, - rs -> rs.next() ? (UUID) rs.getObject("vehicle_id") : null, + rs -> rs.next() + ? new AssignedVehicleReference( + (UUID) rs.getObject("vehicle_id"), + rs.getObject("valid_from", OffsetDateTime.class), + rs.getObject("valid_to", OffsetDateTime.class) + ) + : null, registrationId, occurredAt, occurredAt, @@ -676,4 +689,22 @@ public class VehicleIdentityRepository { } } + public record ResolvedVehicleReferenceResolution( + ResolvedVehicleReference reference, + boolean resolvedFromAssignment, + OffsetDateTime assignmentValidFrom, + OffsetDateTime assignmentValidTo + ) { + public static ResolvedVehicleReferenceResolution empty() { + return new ResolvedVehicleReferenceResolution(ResolvedVehicleReference.empty(), false, null, null); + } + } + + private record AssignedVehicleReference( + UUID vehicleId, + OffsetDateTime validFrom, + OffsetDateTime validTo + ) { + } + }