Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ replay_pid*
/memex/.idea/
/SAMPLE_DATA/VOSA/
/memex/target/
/memex/.gradle/
/DataGen/target/
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;
Expand Down Expand Up @@ -251,8 +253,9 @@ public ResponseEntity<StreamingResponseBody> streamJsonNative() {

@GetMapping(value = "/inspections/asOf", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<StreamingResponseBody> dataAtDate(
@RequestParam(name = "asOfDate") @DateTimeFormat(pattern = "yyyyMMddHHmmss") Date asOfDate,
@RequestParam(name = "asOfDate") @DateTimeFormat(pattern = "yyyyMMddHHmmss") LocalDateTime asOfDateParam,
@RequestParam(name = "id") Long id) {
Instant asOfDate = asOfDateParam.atZone(ZoneOffset.UTC).toInstant();
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

import java.util.Date;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.Map;

Expand Down Expand Up @@ -45,7 +45,7 @@ public class VehicleInspection {
@Version
Long lockVersion;

Date testdate;
LocalDate testdate;
String testclass;
String testtype;
String testresult;
Expand All @@ -58,7 +58,7 @@ public class VehicleInspection {

Long capacity;

Date firstusedate;
LocalDate firstusedate;
/* Use this to flag from the JSON we want to remove the record */
@Transient
@DeleteFlag
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import com.johnlpage.memex.VehicleInspection.model.VehicleInspection;
import com.johnlpage.memex.VehicleInspection.repository.VehicleInspectionRepository;

import java.util.Date;
import java.time.Instant;
import java.util.stream.Stream;

import org.springframework.stereotype.Service;
Expand All @@ -19,7 +19,7 @@ public VehicleInspectionHistoryService(VehicleInspectionRepository repository) {
this.repository = repository;
}

public Stream<VehicleInspection> asOfDate(Long id, Date asOfDate) {
public Stream<VehicleInspection> asOfDate(Long id, Instant asOfDate) {
return repository.GetRecordByIdAsOfDate(id, asOfDate, VehicleInspection.class);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package com.johnlpage.memex.generics.repository;

import java.util.Date;
import java.time.Instant;
import java.util.stream.Stream;

import org.springframework.data.mongodb.core.query.Criteria;

public interface MongoHistoryRepository<T, I> {

Stream<T> GetRecordByIdAsOfDate(I recordId, Date asOf, Class<T> clazz);
Stream<T> GetRecordByIdAsOfDate(I recordId, Instant asOf, Class<T> clazz);

Stream<T> GetRecordsAsOfDate(Criteria criteria, Date asOf, Class<T> clazz);
Stream<T> GetRecordsAsOfDate(Criteria criteria, Instant asOf, Class<T> clazz);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.springframework.data.mongodb.core.aggregation.*;
import org.springframework.data.mongodb.core.query.Criteria;

import java.time.Instant;
import java.util.*;
import java.util.stream.Stream;

Expand All @@ -29,12 +30,12 @@ public MongoHistoryRepositoryImpl(MongoTemplate mongoTemplate, MongoVersionBean
this.mongoVersion = mongoVersion;
}

public Stream<T> GetRecordByIdAsOfDate(I recordId, Date asOf, Class<T> clazz) {
public Stream<T> GetRecordByIdAsOfDate(I recordId, Instant asOf, Class<T> clazz) {
Criteria criteria = Criteria.where("_id").is(recordId);
return GetRecordsAsOfDate(criteria, asOf, clazz);
}

public Stream<T> GetRecordsAsOfDate(Criteria criteria, Date asOf, Class<T> clazz) {
public Stream<T> GetRecordsAsOfDate(Criteria criteria, Instant asOf, Class<T> clazz) {

List<AggregationOperation> stages = new ArrayList<>();
String collectionName = AnnotationExtractor.getCollectionName(clazz);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.fasterxml.jackson.annotation.*;

import java.util.Date;
import java.time.Instant;
import java.util.Map;

import lombok.EqualsAndHashCode;
Expand Down Expand Up @@ -33,7 +33,7 @@ public class DocumentHistory {
@EqualsAndHashCode.Include
ObjectId historyId;
Object recordId;
Date timestamp;
Instant timestamp;
String type; // TOOO enum?
Map<String, Object> changes;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.mongodb.bulk.BulkWriteUpsert;
import com.mongodb.client.ClientSession;

import java.time.Instant;
import java.util.*;

import org.bson.Document;
Expand Down Expand Up @@ -60,7 +61,7 @@ public void postWriteTrigger(
DocumentHistory vih = new DocumentHistory();
vih.setRecordId(v.getId());
vih.setType("insert");
vih.setTimestamp(new Date());
vih.setTimestamp(Instant.now());
history.add(vih); // Add this history records to the history list
}
}
Expand Down Expand Up @@ -103,7 +104,7 @@ public void postWriteTrigger(
previousValues.remove(OptimizedMongoLoadRepositoryImpl.LAST_UPDATE_DATE);

vih.setChanges(previousValues);
vih.setTimestamp(new Date());
vih.setTimestamp(Instant.now());
history.add(vih); // Add this history records to the history list
}
}
Expand All @@ -123,7 +124,7 @@ public void postWriteTrigger(
});
vih.setChanges(finalState);
vih.setType("delete");
vih.setTimestamp(new Date());
vih.setTimestamp(Instant.now());
history.add(vih); // Add this history records to the history list
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,13 @@ private static String mapJavaToBson(Class<?> type) {
if (type == Boolean.class || type == boolean.class) return "bool";
if (Date.class.isAssignableFrom(type)) return "date";
if (type == java.time.Instant.class)
return "date"; // Works as logn as we have a typemapper
return "date"; // Works as long as we have a typemapper
if (type == java.time.LocalDate.class)
return "date"; // Stored as BSON Date via native java.time codecs
if (type == java.time.LocalTime.class)
return "date"; // Stored as BSON Date via native java.time codecs
if (type == java.time.LocalDateTime.class)
return "date"; // Stored as BSON Date via native java.time codecs
if (UUID.class.isAssignableFrom(type)) return "string";

// Native BSON types (preferred for performance)
Expand Down Expand Up @@ -208,7 +214,8 @@ private static String createErrorMessage(Class<?> type) {
if (pkg.startsWith("java.time")) {
return "Unsupported type: "
+ type.getName()
+ ". Convert to java.util.Date or store as ISO-8601 String or epoch Long.";
+ ". Use Instant for timestamps, LocalDate for date-only values, LocalTime for time-only values,"
+ " LocalDateTime for combined date/time, or store as ISO-8601 String or epoch Long.";
}

if (pkg.startsWith("java.sql")) {
Expand Down
22 changes: 10 additions & 12 deletions memex/src/main/java/com/johnlpage/memex/util/ObjectConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ObjectConverter {

Expand All @@ -26,24 +30,18 @@ public static Object convertObject(Object input) {
return ((Number) input).doubleValue();
}
if (input instanceof String str) {
if (!str.isEmpty() && str.length() >= 8 && Character.isDigit(str.charAt(0))) {
if (str.length() >= 8 && Character.isDigit(str.charAt(0))) {
for (DateTimeFormatter formatter : DATE_FORMATTERS) {
try {
// Try parsing as different date/time types
if (str.contains("T")) {
if (str.contains("Z") || str.contains("+") || str.contains("-")) {
return Date.from(ZonedDateTime.parse(str, formatter).toInstant());
if (str.endsWith("Z") || str.matches(".*[+-]\\d{2}(:\\d{2})?$")) {
return ZonedDateTime.parse(str, formatter).toInstant();
} else {
return Date.from(
LocalDateTime.parse(str, formatter)
.atZone(java.time.ZoneOffset.UTC)
.toInstant());
return LocalDateTime.parse(str, formatter).toInstant(ZoneOffset.UTC);
}
} else {
return Date.from(
LocalDate.parse(str, formatter)
.atStartOfDay(java.time.ZoneOffset.UTC)
.toInstant());
return LocalDate.parse(str, formatter);
}
} catch (DateTimeParseException e) {
// Continue to next formatter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.cucumber.java.en.Given;
import org.springframework.beans.factory.annotation.Autowired;

import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;
Expand All @@ -15,7 +16,7 @@ public class TimeManagementSteps {

@Given("I capture the current timestamp to {string} with {string} pattern")
public void iCaptureTheCurrentTimestamp(String macroName, String datePattern) {
ZonedDateTime capturedTimestamp = ZonedDateTime.now();
ZonedDateTime capturedTimestamp = ZonedDateTime.now(ZoneOffset.UTC);
macroRegister.registerMacro(macroName, capturedTimestamp.format(DateTimeFormatter.ofPattern(datePattern)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ Feature: Vehicle Inspection REST API - Point-in-Time History (As Of)
[
{
"testid": 10001,
"testdate": "2025-10-27T11:00:00Z",
"testdate": "2025-10-27",
"testclass": "Class 2",
"testtype": "Interim",
"testresult": "PASS",
"testmileage": 60000,
"postcode": "SW1A 0AB",
"fuel": "Diesel",
"capacity": 90,
"firstusedate": "2019-03-20T00:00:00Z",
"firstusedate": "2019-03-20",
"faileditems": ["Brakes", "Lights"],
"vehicle": {
"make": "Ford",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ Feature: Vehicle Inspection REST API - Data Streaming Capabilities
[
{
"testid": <id1>,
"testdate": "2023-10-26T10:00:00Z",
"testdate": "2023-10-26",
"testclass": "Class 1",
"testtype": "Annual",
"testresult": "PASS",
"testmileage": 50000,
"postcode": "SW1A 0AA",
"fuel": "Petrol",
"capacity": 56,
"firstusedate": "2018-01-15T00:00:00Z",
"firstusedate": "2018-01-15",
"faileditems": [],
"vehicle": {
"make": "Toyota",
Expand All @@ -30,15 +30,15 @@ Feature: Vehicle Inspection REST API - Data Streaming Capabilities
},
{
"testid": <id2>,
"testdate": "2023-10-27T11:00:00Z",
"testdate": "2023-10-27",
"testclass": "Class 2",
"testtype": "Interim",
"testresult": "FAIL",
"testmileage": 60000,
"postcode": "SW1A 0AB",
"fuel": "Diesel",
"capacity": 76,
"firstusedate": "2019-03-20T00:00:00Z",
"firstusedate": "2019-03-20",
"faileditems": ["Brakes", "Lights"],
"vehicle": {
"make": "Ford",
Expand Down Expand Up @@ -88,11 +88,11 @@ Feature: Vehicle Inspection REST API - Data Streaming Capabilities
[
{
"testid": 10001,
"testdate": "2023-10-26T10:00:00Z"
"testdate": "2023-10-26"
},
{ // Malformed object, missing closing brace
"testid": 10002,
"testdate": "2023-10-27T11:00:00Z"
"testdate": "2023-10-27"
]
"""
Then the response status code should be 207
Expand Down
7 changes: 5 additions & 2 deletions memex/templates/controller/Controller.java.template
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import jakarta.servlet.http.HttpServletRequest;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;
Expand Down Expand Up @@ -209,8 +211,9 @@ public class __className__Controller {
*/
@GetMapping(value = "/__apiPath__/asOf", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<StreamingResponseBody> dataAtDate(
@RequestParam(name = "asOfDate") @DateTimeFormat(pattern = "yyyyMMddHHmmss") Date asOfDate,
@RequestParam(name = "asOfDate") @DateTimeFormat(pattern = "yyyyMMddHHmmss") LocalDateTime asOfDateParam,
@RequestParam(name = "id") __idType__ id) {
Instant asOfDate = asOfDateParam.atZone(ZoneOffset.UTC).toInstant();
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(
Expand Down
7 changes: 5 additions & 2 deletions memex/templates/scripts/generate-models-from-json.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -384,8 +384,11 @@ determineType = { String fieldName, Object value, String parentClassName ->
if (value instanceof String) {
String strVal = (String) value

if (strVal ==~ /^\d{4}-\d{2}-\d{2}.*/ || strVal ==~ /.*T\d{2}:\d{2}:\d{2}.*/) {
return [type: 'Date', isComplex: false, imports: ['java.util.Date']]
if (strVal ==~ /.*T\d{2}:\d{2}:\d{2}.*/) {
return [type: 'Instant', isComplex: false, imports: ['java.time.Instant']]
}
if (strVal ==~ /^\d{4}-\d{2}-\d{2}$/) {
return [type: 'LocalDate', isComplex: false, imports: ['java.time.LocalDate']]
}
return [type: 'String', isComplex: false]
}
Expand Down
4 changes: 2 additions & 2 deletions memex/templates/service/HistoryService.java.template
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package __package__.__className__.service;

import __package__.__className__.model.__className__;
import __package__.__className__.repository.__className__Repository;
import java.util.Date;
import java.time.Instant;
import java.util.stream.Stream;
import org.springframework.stereotype.Service;
__idImport__
Expand All @@ -18,7 +18,7 @@ public class __className__HistoryService {
this.repository = repository;
}

public Stream<__className__> asOfDate(__idType__ id, Date asOfDate) {
public Stream<__className__> asOfDate(__idType__ id, Instant asOfDate) {
return repository.GetRecordByIdAsOfDate(id, asOfDate, __className__.class);
}
}
Loading