Commit remaining tracked non-log changes

This commit is contained in:
trifonovt 2026-05-22 15:45:43 +02:00
parent 1128bd3f56
commit dd9c33c5fd
8 changed files with 266 additions and 69 deletions

View File

@ -69,6 +69,10 @@
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-jackson-starter</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>

View File

@ -631,6 +631,121 @@
}
}
]
},
{
"name": "Tachograph composite sessions",
"item": [
{
"name": "Create tachograph composite session",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"sessionIds\": [\n \"{{sessionId}}\",\n \"{{additionalSessionId}}\"\n ],\n \"label\": \"{{compositeSessionLabel}}\"\n}"
},
"url": {
"raw": "{{baseUrl}}/api/eventhub/tachograph-composite-sessions",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"eventhub",
"tachograph-composite-sessions"
]
}
}
},
{
"name": "Get tachograph composite session",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/eventhub/tachograph-composite-sessions/{{compositeSessionId}}",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"eventhub",
"tachograph-composite-sessions",
"{{compositeSessionId}}"
]
}
}
},
{
"name": "List tachograph composite session drivers",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/eventhub/tachograph-composite-sessions/{{compositeSessionId}}/drivers",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"eventhub",
"tachograph-composite-sessions",
"{{compositeSessionId}}",
"drivers"
]
}
}
},
{
"name": "Load tachograph composite driver events",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/eventhub/tachograph-composite-sessions/{{compositeSessionId}}/drivers/{{driverKey}}/events",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"eventhub",
"tachograph-composite-sessions",
"{{compositeSessionId}}",
"drivers",
"{{driverKey}}",
"events"
]
}
}
},
{
"name": "Load tachograph composite driver timeline",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/api/eventhub/tachograph-composite-sessions/{{compositeSessionId}}/drivers/{{driverKey}}/timeline",
"host": [
"{{baseUrl}}"
],
"path": [
"api",
"eventhub",
"tachograph-composite-sessions",
"{{compositeSessionId}}",
"drivers",
"{{driverKey}}",
"timeline"
]
}
}
}
]
}
],
"variable": [
@ -670,6 +785,18 @@
"key": "sessionId",
"value": "00000000-0000-0000-0000-000000000000"
},
{
"key": "additionalSessionId",
"value": "00000000-0000-0000-0000-000000000001"
},
{
"key": "compositeSessionId",
"value": "00000000-0000-0000-0000-000000000100"
},
{
"key": "compositeSessionLabel",
"value": "driver-multi-file sample"
},
{
"key": "driverKey",
"value": "12:12345678901200"

View File

@ -1,6 +1,8 @@
package at.procon.eventhub.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -11,7 +13,9 @@ public class JacksonConfig {
@Bean
@ConditionalOnMissingBean(ObjectMapper.class)
public ObjectMapper objectMapper() {
return new ObjectMapper().findAndRegisterModules();
return new ObjectMapper()
.registerModule(new JavaTimeModule())
.findAndRegisterModules()
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
}

View File

@ -442,16 +442,34 @@ public class EventHubEventReadRepository {
return Integer.parseInt(value.toString());
}
private JsonNode json(String value) {
static JsonNode parseJsonColumn(ObjectMapper objectMapper, String value) {
try {
return value == null || value.isBlank()
JsonNode node = value == null || value.isBlank()
? objectMapper.createObjectNode()
: objectMapper.readTree(value);
while (node != null
&& node.isTextual()
&& looksLikeEmbeddedJson(node.textValue())) {
node = objectMapper.readTree(node.textValue());
}
return node;
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Failed to parse JSON column.", e);
}
}
private JsonNode json(String value) {
return parseJsonColumn(objectMapper, value);
}
private static boolean looksLikeEmbeddedJson(String value) {
if (value == null) {
return false;
}
String trimmed = value.trim();
return trimmed.startsWith("{") || trimmed.startsWith("[");
}
private String syntheticId(String prefix, UUID id) {
return id == null ? null : prefix + ":" + id;
}

View File

@ -1,8 +1,10 @@
package at.procon.eventhub.tachographfilesession.api;
import at.procon.eventhub.tachographfilesession.service.DriverNotFoundInSessionException;
import at.procon.eventhub.tachographfilesession.service.DriverNotFoundInCompositeSessionException;
import at.procon.eventhub.tachographfilesession.service.LegalRequirementsUploadException;
import at.procon.eventhub.tachographfilesession.service.LegalRequirementsXmlDownloadException;
import at.procon.eventhub.tachographfilesession.service.TachographCompositeSessionNotFoundException;
import at.procon.eventhub.tachographfilesession.service.TachographFileSessionNotFoundException;
import at.procon.eventhub.tachographfilesession.service.TachographXmlValidationException;
import at.procon.eventhub.tachographfilesession.service.UnsupportedTachographFileTypeException;
@ -13,12 +15,17 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice(basePackageClasses = TachographFileSessionController.class)
@RestControllerAdvice(basePackageClasses = {
TachographFileSessionController.class,
TachographCompositeSessionController.class
})
public class TachographFileSessionExceptionHandler {
@ExceptionHandler({
TachographFileSessionNotFoundException.class,
DriverNotFoundInSessionException.class
TachographCompositeSessionNotFoundException.class,
DriverNotFoundInSessionException.class,
DriverNotFoundInCompositeSessionException.class
})
public ResponseEntity<Map<String, Object>> notFound(RuntimeException exception) {
return error(HttpStatus.NOT_FOUND, exception);

View File

@ -130,6 +130,7 @@ eventhub:
significant-driving-minutes: 3
merge-gap-seconds: 0
gap-detection-tolerance-seconds: 0
timeline-input-mode: events
legal-requirements:
base-url: ${LEGAL_REQUIREMENTS_BASE_URL:https://legalrequirements.services.bytebar.eu/ODataV4/LR}
username: ${LEGAL_REQUIREMENTS_USERNAME:}

View File

@ -1,60 +1,60 @@
ID;Name;AlphaCode;NumericCode;DefaultLanguageCode;ID_Certificate;ID_FileLog
0;Unknown 0;0;0;NULL;NULL;NULL
1;Austria;A;1;de-AT;NULL;NULL
2;Albania;AL;2;sq-AL;NULL;NULL
3;Andorra;AND;3;ca;NULL;NULL
4;Armenia;ARM;4;hy-AM;NULL;NULL
5;Azerbaijan;AZ;5;az;NULL;NULL
6;Belgium;B;6;NULL;NULL;NULL
7;Bulgaria;BG;7;bg-BG;NULL;NULL
8;Bosnia Herzegovina;BIH;8;NULL;NULL;NULL
9;Belarus;BY;9;be-BY;NULL;NULL
10;Switzerland;CH;10;de-CH;NULL;NULL
11;Cyprus;CY;11;NULL;NULL;NULL
12;Czech Republic;CZ;12;cs-CZ;NULL;NULL
13;Germany;D;13;de-DE;NULL;NULL
14;Denmark;DK;14;da-DK;NULL;NULL
15;Spain;E;15;es-ES;NULL;NULL
16;Estonia;EST;16;et-EE;NULL;NULL
17;France;F;17;fr-FR;NULL;NULL
18;Finland;FIN;18;fi-FI;NULL;NULL
19;Liechtenstein;FL;19;de-LI;NULL;NULL
20;Faroe Islands;FR;20;fo-FO;NULL;NULL
21;United Kingdom;UK;21;en-GB;NULL;NULL
22;Georgia;GE;22;ka-GE;NULL;NULL
23;Greece;GR;23;el-GR;NULL;NULL
24;Hungary;H;24;hu-HU;NULL;NULL
25;Croatia;HR;25;hr-HR;NULL;NULL
26;Italy;I;26;it-IT;NULL;NULL
27;Ireland;IRL;27;en-IE;NULL;NULL
28;Iceland;IS;28;is-IS;NULL;NULL
29;Kazakhstan;KZ;29;kk-KZ;NULL;NULL
30;Luxembourg;L;30;de-LU;NULL;NULL
31;Lithuania;LT;31;lt-LT;NULL;NULL
32;Latvia;LV;32;lv-LV;NULL;NULL
33;Malta;M;33;NULL;NULL;NULL
34;Monaco;MC;34;fr-MC;NULL;NULL
35;Moldova;MD;35;ro;NULL;NULL
36;North Macedonia;MK;36;mk-MK;NULL;NULL
37;Norway;N;37;no;NULL;NULL
38;Netherlands;NL;38;nl-NL;NULL;NULL
39;Portugal;P;39;pt-PT;NULL;NULL
40;Poland;PL;40;pl-PL;NULL;NULL
41;Romania;RO;41;ro-RO;NULL;NULL
42;San Marino;RSM;42;it-IT;NULL;NULL
43;Russia;RUS;43;ru-RU;NULL;NULL
44;Sweden;S;44;sv-SE;NULL;NULL
45;Slovakia;SK;45;sk-SK;NULL;NULL
46;Slovenia;SLO;46;sl-SI;NULL;NULL
47;Turkmenistan;TM;47;NULL;NULL;NULL
48;Turkey;TR;48;tr-TR;NULL;NULL
49;Ukraine;UA;49;uk-UA;NULL;NULL
50;Vatican City;V;50;it-IT;NULL;NULL
51;Yugoslavia;YU;51;NULL;NULL;NULL
52;Montenegro;MNE;52;NULL;NULL;763087
53;Serbia;SRB;53;NULL;NULL;272576
54;Uzbekistan;UZ;54;NULL;NULL;308001
55;Tajikistan;TJ;55;NULL;NULL;NULL
253;European Community;EC;253;NULL;NULL;NULL
254;Rest of Europe;EUR;254;NULL;NULL;NULL
255;Rest of the world;WLD;255;NULL;NULL;NULL
ID;Name;AlphaCode;NumericCode
0;Unknown 0;0;0
1;Austria;A;1
2;Albania;AL;2
3;Andorra;AND;3
4;Armenia;ARM;4
5;Azerbaijan;AZ;5
6;Belgium;B;6
7;Bulgaria;BG;7
8;Bosnia Herzegovina;BIH;8
9;Belarus;BY;9
10;Switzerland;CH;10
11;Cyprus;CY;11
12;Czech Republic;CZ;12
13;Germany;D;13
14;Denmark;DK;14
15;Spain;E;15
16;Estonia;EST;16
17;France;F;17
18;Finland;FIN;18
19;Liechtenstein;FL;19
20;Faroe Islands;FR;20
21;United Kingdom;UK;21
22;Georgia;GE;22
23;Greece;GR;23
24;Hungary;H;24
25;Croatia;HR;25
26;Italy;I;26
27;Ireland;IRL;27
28;Iceland;IS;28
29;Kazakhstan;KZ;29
30;Luxembourg;L;30
31;Lithuania;LT;31
32;Latvia;LV;32
33;Malta;M;33
34;Monaco;MC;34
35;Moldova;MD;35
36;North Macedonia;MK;36
37;Norway;N;37
38;Netherlands;NL;38
39;Portugal;P;39
40;Poland;PL;40
41;Romania;RO;41
42;San Marino;RSM;42
43;Russia;RUS;43
44;Sweden;S;44
45;Slovakia;SK;45
46;Slovenia;SLO;46
47;Turkmenistan;TM;47
48;Turkey;TR;48
49;Ukraine;UA;49
50;Vatican City;V;50
51;Yugoslavia;YU;51
52;Montenegro;MNE;52
53;Serbia;SRB;53
54;Uzbekistan;UZ;54
55;Tajikistan;TJ;55
253;European Community;EC;253
254;Rest of Europe;EUR;254
255;Rest of the world;WLD;255

1 ID Name AlphaCode NumericCode DefaultLanguageCode ID_Certificate ID_FileLog
2 0 Unknown 0 0 0 NULL NULL NULL
3 1 Austria A 1 de-AT NULL NULL
4 2 Albania AL 2 sq-AL NULL NULL
5 3 Andorra AND 3 ca NULL NULL
6 4 Armenia ARM 4 hy-AM NULL NULL
7 5 Azerbaijan AZ 5 az NULL NULL
8 6 Belgium B 6 NULL NULL NULL
9 7 Bulgaria BG 7 bg-BG NULL NULL
10 8 Bosnia Herzegovina BIH 8 NULL NULL NULL
11 9 Belarus BY 9 be-BY NULL NULL
12 10 Switzerland CH 10 de-CH NULL NULL
13 11 Cyprus CY 11 NULL NULL NULL
14 12 Czech Republic CZ 12 cs-CZ NULL NULL
15 13 Germany D 13 de-DE NULL NULL
16 14 Denmark DK 14 da-DK NULL NULL
17 15 Spain E 15 es-ES NULL NULL
18 16 Estonia EST 16 et-EE NULL NULL
19 17 France F 17 fr-FR NULL NULL
20 18 Finland FIN 18 fi-FI NULL NULL
21 19 Liechtenstein FL 19 de-LI NULL NULL
22 20 Faroe Islands FR 20 fo-FO NULL NULL
23 21 United Kingdom UK 21 en-GB NULL NULL
24 22 Georgia GE 22 ka-GE NULL NULL
25 23 Greece GR 23 el-GR NULL NULL
26 24 Hungary H 24 hu-HU NULL NULL
27 25 Croatia HR 25 hr-HR NULL NULL
28 26 Italy I 26 it-IT NULL NULL
29 27 Ireland IRL 27 en-IE NULL NULL
30 28 Iceland IS 28 is-IS NULL NULL
31 29 Kazakhstan KZ 29 kk-KZ NULL NULL
32 30 Luxembourg L 30 de-LU NULL NULL
33 31 Lithuania LT 31 lt-LT NULL NULL
34 32 Latvia LV 32 lv-LV NULL NULL
35 33 Malta M 33 NULL NULL NULL
36 34 Monaco MC 34 fr-MC NULL NULL
37 35 Moldova MD 35 ro NULL NULL
38 36 North Macedonia MK 36 mk-MK NULL NULL
39 37 Norway N 37 no NULL NULL
40 38 Netherlands NL 38 nl-NL NULL NULL
41 39 Portugal P 39 pt-PT NULL NULL
42 40 Poland PL 40 pl-PL NULL NULL
43 41 Romania RO 41 ro-RO NULL NULL
44 42 San Marino RSM 42 it-IT NULL NULL
45 43 Russia RUS 43 ru-RU NULL NULL
46 44 Sweden S 44 sv-SE NULL NULL
47 45 Slovakia SK 45 sk-SK NULL NULL
48 46 Slovenia SLO 46 sl-SI NULL NULL
49 47 Turkmenistan TM 47 NULL NULL NULL
50 48 Turkey TR 48 tr-TR NULL NULL
51 49 Ukraine UA 49 uk-UA NULL NULL
52 50 Vatican City V 50 it-IT NULL NULL
53 51 Yugoslavia YU 51 NULL NULL NULL
54 52 Montenegro MNE 52 NULL NULL 763087
55 53 Serbia SRB 53 NULL NULL 272576
56 54 Uzbekistan UZ 54 NULL NULL 308001
57 55 Tajikistan TJ 55 NULL NULL NULL
58 253 European Community EC 253 NULL NULL NULL
59 254 Rest of Europe EUR 254 NULL NULL NULL
60 255 Rest of the world WLD 255 NULL NULL NULL

View File

@ -6,6 +6,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import at.procon.eventhub.dto.EventDetailsDto;
import at.procon.eventhub.dto.DriverRefDto;
import at.procon.eventhub.dto.EventDomain;
import at.procon.eventhub.dto.EventHubEventDto;
@ -23,21 +24,32 @@ import at.procon.eventhub.processing.service.UnifiedRuntimeEventAssemblyService;
import at.procon.eventhub.tachographfilesession.model.ResolvedActivityInterval;
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
import at.procon.eventhub.tachographfilesession.model.ResolvedVehicleUsageInterval;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer;
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.junit.jupiter.api.Test;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
class UnifiedRuntimeProcessingControllerTest {
private final ObjectMapper objectMapper = testObjectMapper();
@Test
void loadsDriverEventsViaRuntimeApi() throws Exception {
UnifiedRuntimeEventAssemblyService eventAssemblyService = org.mockito.Mockito.mock(UnifiedRuntimeEventAssemblyService.class);
UnifiedRuntimeDriverTimelineService timelineService = org.mockito.Mockito.mock(UnifiedRuntimeDriverTimelineService.class);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new UnifiedRuntimeProcessingController(eventAssemblyService, timelineService))
.setMessageConverters(new MappingJackson2HttpMessageConverter(objectMapper))
.setControllerAdvice(new UnifiedRuntimeProcessingExceptionHandler())
.build();
@ -77,7 +89,9 @@ class UnifiedRuntimeProcessingControllerTest {
.andExpect(jsonPath("$.request.sessionId").value(sessionId.toString()))
.andExpect(jsonPath("$.request.driverKey").value("12:123"))
.andExpect(jsonPath("$.discoveredVehicles[0].vin").value("VIN-1"))
.andExpect(jsonPath("$.mergedEvents[0].externalSourceEventId").value("EV-1"));
.andExpect(jsonPath("$.mergedEvents[0].externalSourceEventId").value("EV-1"))
.andExpect(jsonPath("$.mergedEvents[0].payload.raw.driverKey").value("12:123"))
.andExpect(jsonPath("$.mergedEvents[0].eventDetails.attributes.cardSlot").value("DRIVER"));
}
@Test
@ -85,6 +99,7 @@ class UnifiedRuntimeProcessingControllerTest {
UnifiedRuntimeEventAssemblyService eventAssemblyService = org.mockito.Mockito.mock(UnifiedRuntimeEventAssemblyService.class);
UnifiedRuntimeDriverTimelineService timelineService = org.mockito.Mockito.mock(UnifiedRuntimeDriverTimelineService.class);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new UnifiedRuntimeProcessingController(eventAssemblyService, timelineService))
.setMessageConverters(new MappingJackson2HttpMessageConverter(objectMapper))
.setControllerAdvice(new UnifiedRuntimeProcessingExceptionHandler())
.build();
@ -152,6 +167,7 @@ class UnifiedRuntimeProcessingControllerTest {
UnifiedRuntimeEventAssemblyService eventAssemblyService = org.mockito.Mockito.mock(UnifiedRuntimeEventAssemblyService.class);
UnifiedRuntimeDriverTimelineService timelineService = org.mockito.Mockito.mock(UnifiedRuntimeDriverTimelineService.class);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new UnifiedRuntimeProcessingController(eventAssemblyService, timelineService))
.setMessageConverters(new MappingJackson2HttpMessageConverter(objectMapper))
.setControllerAdvice(new UnifiedRuntimeProcessingExceptionHandler())
.build();
@ -182,11 +198,31 @@ class UnifiedRuntimeProcessingControllerTest {
EventLifecycle.START,
null,
null,
new EventDetailsDto("DRIVER_ACTIVITY", objectMapper.valueToTree(Map.of("cardSlot", "DRIVER"))),
null,
null,
null,
objectMapper.valueToTree(Map.of("raw", Map.of("driverKey", "12:123", "intervalId", "ACT-1"))),
false,
null
);
}
private ObjectMapper testObjectMapper() {
SimpleModule module = new SimpleModule();
module.addSerializer(OffsetDateTime.class, new StdScalarSerializer<>(OffsetDateTime.class) {
@Override
public void serialize(OffsetDateTime value, JsonGenerator gen, com.fasterxml.jackson.databind.SerializerProvider provider)
throws java.io.IOException {
gen.writeString(value == null ? null : value.toString());
}
});
module.addDeserializer(OffsetDateTime.class, new StdScalarDeserializer<>(OffsetDateTime.class) {
@Override
public OffsetDateTime deserialize(JsonParser p, com.fasterxml.jackson.databind.DeserializationContext ctxt)
throws java.io.IOException {
String value = p.getValueAsString();
return value == null || value.isBlank() ? null : OffsetDateTime.parse(value);
}
});
return new ObjectMapper().registerModule(module);
}
}