refactor(core,common,proprietary): Replace Date with Instant/modern Date API alternative for improved time handling (#4497)

This commit is contained in:
Balázs Szücs 2025-09-28 17:41:20 +02:00 committed by GitHub
parent 07392ed25e
commit 133e6d3de6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 244 additions and 125 deletions

View File

@ -1,6 +1,6 @@
package stirling.software.common.model; package stirling.software.common.model;
import java.util.Calendar; import java.time.ZonedDateTime;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
@ -18,6 +18,6 @@ public class PdfMetadata {
private String creator; private String creator;
private String subject; private String subject;
private String keywords; private String keywords;
private Calendar creationDate; private ZonedDateTime creationDate;
private Calendar modificationDate; private ZonedDateTime modificationDate;
} }

View File

@ -1,5 +1,9 @@
package stirling.software.common.service; package stirling.software.common.service;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Calendar; import java.util.Calendar;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
@ -29,17 +33,19 @@ public class PdfMetadataService {
this.runningProOrHigher = runningProOrHigher; this.runningProOrHigher = runningProOrHigher;
} }
public PdfMetadata extractMetadataFromPdf(PDDocument pdf) { /**
return PdfMetadata.builder() * Converts ZonedDateTime to Calendar for PDFBox compatibility.
.author(pdf.getDocumentInformation().getAuthor()) *
.producer(pdf.getDocumentInformation().getProducer()) * @param zonedDateTime the ZonedDateTime to convert
.title(pdf.getDocumentInformation().getTitle()) * @return Calendar instance or null if input is null
.creator(pdf.getDocumentInformation().getCreator()) */
.subject(pdf.getDocumentInformation().getSubject()) public static Calendar toCalendar(ZonedDateTime zonedDateTime) {
.keywords(pdf.getDocumentInformation().getKeywords()) if (zonedDateTime == null) {
.creationDate(pdf.getDocumentInformation().getCreationDate()) return null;
.modificationDate(pdf.getDocumentInformation().getModificationDate()) }
.build(); Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(zonedDateTime.toInstant().toEpochMilli());
return calendar;
} }
public void setDefaultMetadata(PDDocument pdf) { public void setDefaultMetadata(PDDocument pdf) {
@ -58,6 +64,52 @@ public class PdfMetadataService {
setCommonMetadata(pdf, pdfMetadata); setCommonMetadata(pdf, pdfMetadata);
} }
/**
* Parses a date string and converts it to Calendar for PDFBox compatibility.
*
* @param dateString the date string in "yyyy/MM/dd HH:mm:ss" format
* @return Calendar instance or null if parsing fails or input is empty
*/
public static Calendar parseToCalendar(String dateString) {
if (dateString == null || dateString.trim().isEmpty()) {
return null;
}
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
ZonedDateTime zonedDateTime =
LocalDateTime.parse(dateString, formatter).atZone(ZoneId.systemDefault());
return toCalendar(zonedDateTime);
} catch (Exception e) {
return null;
}
}
public PdfMetadata extractMetadataFromPdf(PDDocument pdf) {
Calendar creationCal = pdf.getDocumentInformation().getCreationDate();
Calendar modificationCal = pdf.getDocumentInformation().getModificationDate();
ZonedDateTime creationDate =
creationCal != null
? ZonedDateTime.ofInstant(creationCal.toInstant(), ZoneId.systemDefault())
: null;
ZonedDateTime modificationDate =
modificationCal != null
? ZonedDateTime.ofInstant(
modificationCal.toInstant(), ZoneId.systemDefault())
: null;
return PdfMetadata.builder()
.author(pdf.getDocumentInformation().getAuthor())
.producer(pdf.getDocumentInformation().getProducer())
.title(pdf.getDocumentInformation().getTitle())
.creator(pdf.getDocumentInformation().getCreator())
.subject(pdf.getDocumentInformation().getSubject())
.keywords(pdf.getDocumentInformation().getKeywords())
.creationDate(creationDate)
.modificationDate(modificationDate)
.build();
}
private void setNewDocumentMetadata(PDDocument pdf, PdfMetadata pdfMetadata) { private void setNewDocumentMetadata(PDDocument pdf, PdfMetadata pdfMetadata) {
String creator = stirlingPDFLabel; String creator = stirlingPDFLabel;
@ -79,7 +131,13 @@ public class PdfMetadataService {
} }
pdf.getDocumentInformation().setCreator(creator); pdf.getDocumentInformation().setCreator(creator);
pdf.getDocumentInformation().setCreationDate(Calendar.getInstance());
// Use existing creation date if available, otherwise create new one
Calendar creationCal =
pdfMetadata.getCreationDate() != null
? toCalendar(pdfMetadata.getCreationDate())
: Calendar.getInstance();
pdf.getDocumentInformation().setCreationDate(creationCal);
} }
private void setCommonMetadata(PDDocument pdf, PdfMetadata pdfMetadata) { private void setCommonMetadata(PDDocument pdf, PdfMetadata pdfMetadata) {
@ -88,7 +146,13 @@ public class PdfMetadataService {
pdf.getDocumentInformation().setProducer(stirlingPDFLabel); pdf.getDocumentInformation().setProducer(stirlingPDFLabel);
pdf.getDocumentInformation().setSubject(pdfMetadata.getSubject()); pdf.getDocumentInformation().setSubject(pdfMetadata.getSubject());
pdf.getDocumentInformation().setKeywords(pdfMetadata.getKeywords()); pdf.getDocumentInformation().setKeywords(pdfMetadata.getKeywords());
pdf.getDocumentInformation().setModificationDate(Calendar.getInstance());
// Convert ZonedDateTime to Calendar for PDFBox compatibility
Calendar modificationCal =
pdfMetadata.getModificationDate() != null
? toCalendar(pdfMetadata.getModificationDate())
: Calendar.getInstance();
pdf.getDocumentInformation().setModificationDate(modificationCal);
String author = pdfMetadata.getAuthor(); String author = pdfMetadata.getAuthor();
if (applicationProperties if (applicationProperties

View File

@ -6,6 +6,8 @@ import java.io.InputStream;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@ -145,7 +147,11 @@ public class EmlParser {
extractRecipients(message, messageClass, content); extractRecipients(message, messageClass, content);
Method getSentDate = messageClass.getMethod("getSentDate"); Method getSentDate = messageClass.getMethod("getSentDate");
content.setDate((Date) getSentDate.invoke(message)); Date legacyDate = (Date) getSentDate.invoke(message);
if (legacyDate != null) {
content.setDate(
ZonedDateTime.ofInstant(legacyDate.toInstant(), ZoneId.systemDefault()));
}
Method getContent = messageClass.getMethod("getContent"); Method getContent = messageClass.getMethod("getContent");
Object messageContent = getContent.invoke(message); Object messageContent = getContent.invoke(message);
@ -616,7 +622,7 @@ public class EmlParser {
private String to; private String to;
private String cc; private String cc;
private String bcc; private String bcc;
private Date date; private ZonedDateTime date;
private String dateString; // For basic parsing fallback private String dateString; // For basic parsing fallback
private String htmlBody; private String htmlBody;
private String textBody; private String textBody;

View File

@ -8,7 +8,9 @@ import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.text.SimpleDateFormat; import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;
import java.util.Date; import java.util.Date;
@ -18,7 +20,6 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TimeZone;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -290,11 +291,15 @@ public class PdfAttachmentHandler {
public static String formatEmailDate(Date date) { public static String formatEmailDate(Date date) {
if (date == null) return ""; if (date == null) return "";
return formatEmailDate(ZonedDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()));
}
SimpleDateFormat formatter = public static String formatEmailDate(ZonedDateTime dateTime) {
new SimpleDateFormat("EEE, MMM d, yyyy 'at' h:mm a z", Locale.ENGLISH); if (dateTime == null) return "";
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
return formatter.format(date); DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("EEE, MMM d, yyyy 'at' h:mm a z", Locale.ENGLISH);
return dateTime.withZoneSameInstant(ZoneId.of("UTC")).format(formatter);
} }
@Data @Data

View File

@ -7,16 +7,18 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.TimeZone;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.pdfbox.Loader; import org.apache.pdfbox.Loader;
@ -564,18 +566,31 @@ public class ConvertPDFToPDFA {
adobePdfSchema.setKeywords(keywords); adobePdfSchema.setKeywords(keywords);
} }
// Set creation and modification dates // Set creation and modification dates using java.time and convert to GregorianCalendar
Calendar now = Calendar.getInstance(TimeZone.getTimeZone("UTC")); Instant nowInstant = Instant.now();
Calendar originalCreationDate = docInfo.getCreationDate(); ZonedDateTime nowZdt = ZonedDateTime.ofInstant(nowInstant, ZoneId.of("UTC"));
if (originalCreationDate == null) { GregorianCalendar nowCal = GregorianCalendar.from(nowZdt);
originalCreationDate = now;
}
docInfo.setCreationDate(originalCreationDate);
xmpBasicSchema.setCreateDate(originalCreationDate);
docInfo.setModificationDate(now); java.util.Calendar originalCreationDate = docInfo.getCreationDate();
xmpBasicSchema.setModifyDate(now); GregorianCalendar creationCal;
xmpBasicSchema.setMetadataDate(now); if (originalCreationDate == null) {
creationCal = nowCal;
} else if (originalCreationDate instanceof GregorianCalendar) {
creationCal = (GregorianCalendar) originalCreationDate;
} else {
// convert other Calendar implementations to GregorianCalendar preserving instant
creationCal =
GregorianCalendar.from(
ZonedDateTime.ofInstant(
originalCreationDate.toInstant(), ZoneId.of("UTC")));
}
docInfo.setCreationDate(creationCal);
xmpBasicSchema.setCreateDate(creationCal);
docInfo.setModificationDate(nowCal);
xmpBasicSchema.setModifyDate(nowCal);
xmpBasicSchema.setMetadataDate(nowCal);
// Serialize the created metadata so it can be attached to the existent metadata // Serialize the created metadata so it can be attached to the existent metadata
ByteArrayOutputStream xmpOut = new ByteArrayOutputStream(); ByteArrayOutputStream xmpOut = new ByteArrayOutputStream();

View File

@ -1,8 +1,6 @@
package stirling.software.SPDF.controller.api.misc; package stirling.software.SPDF.controller.api.misc;
import java.io.IOException; import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar; import java.util.Calendar;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@ -25,6 +23,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.MetadataRequest; import stirling.software.SPDF.model.api.misc.MetadataRequest;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.service.PdfMetadataService;
import stirling.software.common.util.WebResponseUtils; import stirling.software.common.util.WebResponseUtils;
import stirling.software.common.util.propertyeditor.StringToMapPropertyEditor; import stirling.software.common.util.propertyeditor.StringToMapPropertyEditor;
@ -144,30 +143,13 @@ public class MetadataController {
} }
} }
} }
if (creationDate != null && creationDate.length() > 0) { // Set creation date using utility method
Calendar creationDateCal = Calendar.getInstance(); Calendar creationDateCal = PdfMetadataService.parseToCalendar(creationDate);
try { info.setCreationDate(creationDateCal);
creationDateCal.setTime(
new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(creationDate)); // Set modification date using utility method
} catch (ParseException e) { Calendar modificationDateCal = PdfMetadataService.parseToCalendar(modificationDate);
log.error("exception", e); info.setModificationDate(modificationDateCal);
}
info.setCreationDate(creationDateCal);
} else {
info.setCreationDate(null);
}
if (modificationDate != null && modificationDate.length() > 0) {
Calendar modificationDateCal = Calendar.getInstance();
try {
modificationDateCal.setTime(
new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(modificationDate));
} catch (ParseException e) {
log.error("exception", e);
}
info.setModificationDate(modificationDateCal);
} else {
info.setModificationDate(null);
}
info.setCreator(creator); info.setCreator(creator);
info.setKeywords(keywords); info.setKeywords(keywords);
info.setAuthor(author); info.setAuthor(author);

View File

@ -120,7 +120,7 @@ public class CertSignController {
signature.setName(name); signature.setName(name);
signature.setLocation(location); signature.setLocation(location);
signature.setReason(reason); signature.setReason(reason);
signature.setSignDate(Calendar.getInstance()); signature.setSignDate(Calendar.getInstance()); // PDFBox requires Calendar
if (Boolean.TRUE.equals(showSignature)) { if (Boolean.TRUE.equals(showSignature)) {
SignatureOptions signatureOptions = new SignatureOptions(); SignatureOptions signatureOptions = new SignatureOptions();
signatureOptions.setVisualSignature( signatureOptions.setVisualSignature(

View File

@ -3,7 +3,9 @@ package stirling.software.SPDF.controller.api.security;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat; import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
import org.apache.pdfbox.cos.COSInputStream; import org.apache.pdfbox.cos.COSInputStream;
@ -817,8 +819,10 @@ public class GetInfoOnPDF {
private String formatDate(Calendar calendar) { private String formatDate(Calendar calendar) {
if (calendar != null) { if (calendar != null) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return sdf.format(calendar.getTime()); ZonedDateTime zonedDateTime =
ZonedDateTime.ofInstant(calendar.toInstant(), ZoneId.systemDefault());
return zonedDateTime.format(formatter);
} else { } else {
return null; return null;
} }

View File

@ -7,8 +7,8 @@ import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey; import java.security.interfaces.RSAPublicKey;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.List; import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
@ -134,11 +134,12 @@ public class ValidateSignatureController {
: certValidationService.validateTrustStore(cert)); : certValidationService.validateTrustStore(cert));
result.setNotRevoked(!certValidationService.isRevoked(cert)); result.setNotRevoked(!certValidationService.isRevoked(cert));
result.setNotExpired(!cert.getNotAfter().before(new Date())); result.setNotExpired(
Instant.now().isBefore(cert.getNotAfter().toInstant()));
// Set basic signature info // Set basic signature info
result.setSignerName(sig.getName()); result.setSignerName(sig.getName());
result.setSignatureDate(sig.getSignDate().getTime().toString()); result.setSignatureDate(sig.getSignDate().toInstant().toString());
result.setReason(sig.getReason()); result.setReason(sig.getReason());
result.setLocation(sig.getLocation()); result.setLocation(sig.getLocation());

View File

@ -3,6 +3,9 @@ package stirling.software.SPDF.service;
import static stirling.software.common.util.AttachmentUtils.setCatalogViewerPreferences; import static stirling.software.common.util.AttachmentUtils.setCatalogViewerPreferences;
import java.io.IOException; import java.io.IOException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -54,8 +57,13 @@ public class AttachmentService implements AttachmentServiceInterface {
PDEmbeddedFile embeddedFile = PDEmbeddedFile embeddedFile =
new PDEmbeddedFile(document, attachment.getInputStream()); new PDEmbeddedFile(document, attachment.getInputStream());
embeddedFile.setSize((int) attachment.getSize()); embeddedFile.setSize((int) attachment.getSize());
embeddedFile.setCreationDate(new GregorianCalendar()); // use java.time.Instant and convert to GregorianCalendar for PDFBox
embeddedFile.setModDate(new GregorianCalendar()); Instant now = Instant.now();
GregorianCalendar nowCal =
GregorianCalendar.from(
ZonedDateTime.ofInstant(now, ZoneId.systemDefault()));
embeddedFile.setCreationDate(nowCal);
embeddedFile.setModDate(nowCal);
String contentType = attachment.getContentType(); String contentType = attachment.getContentType();
if (StringUtils.isNotBlank(contentType)) { if (StringUtils.isNotBlank(contentType)) {
embeddedFile.setSubtype(contentType); embeddedFile.setSubtype(contentType);

View File

@ -7,6 +7,8 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar; import java.util.Calendar;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
@ -81,6 +83,12 @@ class PdfMetadataServiceBasicTest {
// Act // Act
PdfMetadata metadata = pdfMetadataService.extractMetadataFromPdf(testDocument); PdfMetadata metadata = pdfMetadataService.extractMetadataFromPdf(testDocument);
// Convert Calendar to ZonedDateTime for comparison
ZonedDateTime expectedCreationDate =
ZonedDateTime.ofInstant(creationDate.toInstant(), ZoneId.systemDefault());
ZonedDateTime expectedModificationDate =
ZonedDateTime.ofInstant(modificationDate.toInstant(), ZoneId.systemDefault());
// Assert // Assert
assertEquals(testAuthor, metadata.getAuthor(), "Author should match"); assertEquals(testAuthor, metadata.getAuthor(), "Author should match");
assertEquals(testProducer, metadata.getProducer(), "Producer should match"); assertEquals(testProducer, metadata.getProducer(), "Producer should match");
@ -88,9 +96,12 @@ class PdfMetadataServiceBasicTest {
assertEquals(testCreator, metadata.getCreator(), "Creator should match"); assertEquals(testCreator, metadata.getCreator(), "Creator should match");
assertEquals(testSubject, metadata.getSubject(), "Subject should match"); assertEquals(testSubject, metadata.getSubject(), "Subject should match");
assertEquals(testKeywords, metadata.getKeywords(), "Keywords should match"); assertEquals(testKeywords, metadata.getKeywords(), "Keywords should match");
assertEquals(creationDate, metadata.getCreationDate(), "Creation date should match");
assertEquals( assertEquals(
modificationDate, metadata.getModificationDate(), "Modification date should match"); expectedCreationDate, metadata.getCreationDate(), "Creation date should match");
assertEquals(
expectedModificationDate,
metadata.getModificationDate(),
"Modification date should match");
} }
@Test @Test

View File

@ -7,6 +7,8 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar; import java.util.Calendar;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
@ -84,6 +86,12 @@ class PdfMetadataServiceTest {
// Act // Act
PdfMetadata metadata = pdfMetadataService.extractMetadataFromPdf(testDocument); PdfMetadata metadata = pdfMetadataService.extractMetadataFromPdf(testDocument);
// Convert Calendar to ZonedDateTime for comparison
ZonedDateTime expectedCreationDate =
ZonedDateTime.ofInstant(creationDate.toInstant(), ZoneId.systemDefault());
ZonedDateTime expectedModificationDate =
ZonedDateTime.ofInstant(modificationDate.toInstant(), ZoneId.systemDefault());
// Assert // Assert
assertEquals(testAuthor, metadata.getAuthor(), "Author should match"); assertEquals(testAuthor, metadata.getAuthor(), "Author should match");
assertEquals(testProducer, metadata.getProducer(), "Producer should match"); assertEquals(testProducer, metadata.getProducer(), "Producer should match");
@ -91,9 +99,12 @@ class PdfMetadataServiceTest {
assertEquals(testCreator, metadata.getCreator(), "Creator should match"); assertEquals(testCreator, metadata.getCreator(), "Creator should match");
assertEquals(testSubject, metadata.getSubject(), "Subject should match"); assertEquals(testSubject, metadata.getSubject(), "Subject should match");
assertEquals(testKeywords, metadata.getKeywords(), "Keywords should match"); assertEquals(testKeywords, metadata.getKeywords(), "Keywords should match");
assertEquals(creationDate, metadata.getCreationDate(), "Creation date should match");
assertEquals( assertEquals(
modificationDate, metadata.getModificationDate(), "Modification date should match"); expectedCreationDate, metadata.getCreationDate(), "Creation date should match");
assertEquals(
expectedModificationDate,
metadata.getModificationDate(),
"Modification date should match");
} }
@Test @Test
@ -190,6 +201,8 @@ class PdfMetadataServiceTest {
// Prepare test metadata with existing creation date // Prepare test metadata with existing creation date
Calendar existingCreationDate = Calendar.getInstance(); Calendar existingCreationDate = Calendar.getInstance();
existingCreationDate.add(Calendar.DAY_OF_MONTH, -1); // Yesterday existingCreationDate.add(Calendar.DAY_OF_MONTH, -1); // Yesterday
ZonedDateTime existingCreationDateZdt =
ZonedDateTime.ofInstant(existingCreationDate.toInstant(), ZoneId.systemDefault());
PdfMetadata testMetadata = PdfMetadata testMetadata =
PdfMetadata.builder() PdfMetadata.builder()
@ -197,7 +210,7 @@ class PdfMetadataServiceTest {
.title("Test Title") .title("Test Title")
.subject("Test Subject") .subject("Test Subject")
.keywords("Test Keywords") .keywords("Test Keywords")
.creationDate(existingCreationDate) .creationDate(existingCreationDateZdt)
.build(); .build();
// Act // Act

View File

@ -4,7 +4,6 @@ import static stirling.software.common.util.ProviderUtils.validateProvider;
import java.time.Instant; import java.time.Instant;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -218,7 +217,7 @@ public class AccountWebController {
Map<String, String> roleDetails = Role.getAllRoleDetails(); Map<String, String> roleDetails = Role.getAllRoleDetails();
// Map to store session information and user activity status // Map to store session information and user activity status
Map<String, Boolean> userSessions = new HashMap<>(); Map<String, Boolean> userSessions = new HashMap<>();
Map<String, Date> userLastRequest = new HashMap<>(); Map<String, Instant> userLastRequest = new HashMap<>();
int activeUsers = 0; int activeUsers = 0;
int disabledUsers = 0; int disabledUsers = 0;
while (iterator.hasNext()) { while (iterator.hasNext()) {
@ -249,27 +248,29 @@ public class AccountWebController {
// Determine the user's session status and last request time // Determine the user's session status and last request time
int maxInactiveInterval = sessionPersistentRegistry.getMaxInactiveInterval(); int maxInactiveInterval = sessionPersistentRegistry.getMaxInactiveInterval();
boolean hasActiveSession = false; boolean hasActiveSession = false;
Date lastRequest = null; Instant lastRequest = null;
Optional<SessionEntity> latestSession = Optional<SessionEntity> latestSession =
sessionPersistentRegistry.findLatestSession(user.getUsername()); sessionPersistentRegistry.findLatestSession(user.getUsername());
if (latestSession.isPresent()) { if (latestSession.isPresent()) {
SessionEntity sessionEntity = latestSession.get(); SessionEntity sessionEntity = latestSession.get();
Date lastAccessedTime = sessionEntity.getLastRequest(); // sessionEntity stores Instant directly
Instant lastAccessedTime =
Optional.ofNullable(sessionEntity.getLastRequest())
.orElse(Instant.EPOCH);
Instant now = Instant.now(); Instant now = Instant.now();
// Calculate session expiration and update session status accordingly // Calculate session expiration and update session status accordingly
Instant expirationTime = Instant expirationTime =
lastAccessedTime lastAccessedTime.plus(maxInactiveInterval, ChronoUnit.SECONDS);
.toInstant()
.plus(maxInactiveInterval, ChronoUnit.SECONDS);
if (now.isAfter(expirationTime)) { if (now.isAfter(expirationTime)) {
sessionPersistentRegistry.expireSession(sessionEntity.getSessionId()); sessionPersistentRegistry.expireSession(sessionEntity.getSessionId());
} else { } else {
hasActiveSession = !sessionEntity.isExpired(); hasActiveSession = !sessionEntity.isExpired();
} }
lastRequest = sessionEntity.getLastRequest(); lastRequest = lastAccessedTime;
} else { } else {
// No session, set default last request time // No session, set default last request time
lastRequest = new Date(0); lastRequest = Instant.EPOCH;
} }
userSessions.put(user.getUsername(), hasActiveSession); userSessions.put(user.getUsername(), hasActiveSession);
userLastRequest.put(user.getUsername(), lastRequest); userLastRequest.put(user.getUsername(), lastRequest);
@ -286,19 +287,21 @@ public class AccountWebController {
allUsers.stream() allUsers.stream()
.sorted( .sorted(
(u1, u2) -> { (u1, u2) -> {
boolean u1Active = userSessions.get(u1.getUsername()); boolean u1Active =
boolean u2Active = userSessions.get(u2.getUsername()); userSessions.getOrDefault(u1.getUsername(), false);
boolean u2Active =
userSessions.getOrDefault(u2.getUsername(), false);
if (u1Active && !u2Active) { if (u1Active && !u2Active) {
return -1; return -1;
} else if (!u1Active && u2Active) { } else if (!u1Active && u2Active) {
return 1; return 1;
} else { } else {
Date u1LastRequest = Instant u1LastRequest =
userLastRequest.getOrDefault( userLastRequest.getOrDefault(
u1.getUsername(), new Date(0)); u1.getUsername(), Instant.EPOCH);
Date u2LastRequest = Instant u2LastRequest =
userLastRequest.getOrDefault( userLastRequest.getOrDefault(
u2.getUsername(), new Date(0)); u2.getUsername(), Instant.EPOCH);
return u2LastRequest.compareTo(u1LastRequest); return u2LastRequest.compareTo(u1LastRequest);
} }
}) })

View File

@ -1,6 +1,6 @@
package stirling.software.proprietary.security.controller.web; package stirling.software.proprietary.security.controller.web;
import java.util.Date; import java.time.Instant;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -44,17 +44,17 @@ public class TeamWebController {
// Filter out the Internal team // Filter out the Internal team
List<TeamWithUserCountDTO> teamsWithCounts = List<TeamWithUserCountDTO> teamsWithCounts =
allTeamsWithCounts.stream() allTeamsWithCounts.stream()
.filter(team -> !team.getName().equals(TeamService.INTERNAL_TEAM_NAME)) .filter(team -> !TeamService.INTERNAL_TEAM_NAME.equals(team.getName()))
.toList(); .toList();
// Get the latest activity for each team // Get the latest activity for each team
List<Object[]> teamActivities = sessionRepository.findLatestActivityByTeam(); List<Object[]> teamActivities = sessionRepository.findLatestActivityByTeam();
// Convert the query results to a map for easy access in the view // Convert the query results to a map for easy access in the view
Map<Long, Date> teamLastRequest = new HashMap<>(); Map<Long, Instant> teamLastRequest = new HashMap<>();
for (Object[] result : teamActivities) { for (Object[] result : teamActivities) {
Long teamId = (Long) result[0]; // teamId alias Long teamId = (Long) result[0]; // teamId alias
Date lastActivity = (Date) result[1]; // lastActivity alias Instant lastActivity = (Instant) result[1]; // lastActivity alias
teamLastRequest.put(teamId, lastActivity); teamLastRequest.put(teamId, lastActivity);
} }
@ -97,7 +97,7 @@ public class TeamWebController {
.orElseThrow(() -> new RuntimeException("Team not found")); .orElseThrow(() -> new RuntimeException("Team not found"));
// Prevent access to Internal team // Prevent access to Internal team
if (team.getName().equals(TeamService.INTERNAL_TEAM_NAME)) { if (TeamService.INTERNAL_TEAM_NAME.equals(team.getName())) {
return "redirect:/teams?error=internalTeamNotAccessible"; return "redirect:/teams?error=internalTeamNotAccessible";
} }
@ -114,21 +114,18 @@ public class TeamWebController {
(user.getTeam() == null (user.getTeam() == null
|| !user.getTeam().getId().equals(id)) || !user.getTeam().getId().equals(id))
&& (user.getTeam() == null && (user.getTeam() == null
|| !user.getTeam() || !TeamService.INTERNAL_TEAM_NAME.equals(
.getName() user.getTeam().getName())))
.equals(
TeamService
.INTERNAL_TEAM_NAME)))
.toList(); .toList();
// Get the latest session for each user in the team // Get the latest session for each user in the team
List<Object[]> userSessions = sessionRepository.findLatestSessionByTeamId(id); List<Object[]> userSessions = sessionRepository.findLatestSessionByTeamId(id);
// Create a map of username to last request date // Create a map of username to last request date
Map<String, Date> userLastRequest = new HashMap<>(); Map<String, Instant> userLastRequest = new HashMap<>();
for (Object[] result : userSessions) { for (Object[] result : userSessions) {
String username = (String) result[0]; // username alias String username = (String) result[0]; // username alias
Date lastRequest = (Date) result[1]; // lastRequest alias Instant lastRequest = (Instant) result[1]; // lastRequest alias
userLastRequest.put(username, lastRequest); userLastRequest.put(username, lastRequest);
} }

View File

@ -23,7 +23,7 @@ public class JPATokenRepositoryImpl implements PersistentTokenRepository {
newToken.setSeries(token.getSeries()); newToken.setSeries(token.getSeries());
newToken.setUsername(token.getUsername()); newToken.setUsername(token.getUsername());
newToken.setToken(token.getTokenValue()); newToken.setToken(token.getTokenValue());
newToken.setLastUsed(token.getDate()); newToken.setLastUsed(token.getDate().toInstant());
persistentLoginRepository.save(newToken); persistentLoginRepository.save(newToken);
} }
@ -33,7 +33,7 @@ public class JPATokenRepositoryImpl implements PersistentTokenRepository {
PersistentLogin existingToken = persistentLoginRepository.findById(series).orElse(null); PersistentLogin existingToken = persistentLoginRepository.findById(series).orElse(null);
if (existingToken != null) { if (existingToken != null) {
existingToken.setToken(tokenValue); existingToken.setToken(tokenValue);
existingToken.setLastUsed(lastUsed); existingToken.setLastUsed(lastUsed.toInstant());
persistentLoginRepository.save(existingToken); persistentLoginRepository.save(existingToken);
} }
} }
@ -43,7 +43,10 @@ public class JPATokenRepositoryImpl implements PersistentTokenRepository {
PersistentLogin token = persistentLoginRepository.findById(seriesId).orElse(null); PersistentLogin token = persistentLoginRepository.findById(seriesId).orElse(null);
if (token != null) { if (token != null) {
return new PersistentRememberMeToken( return new PersistentRememberMeToken(
token.getUsername(), token.getSeries(), token.getToken(), token.getLastUsed()); token.getUsername(),
token.getSeries(),
token.getToken(),
Date.from(token.getLastUsed()));
} }
return null; return null;
} }

View File

@ -1,6 +1,6 @@
package stirling.software.proprietary.security.database.repository; package stirling.software.proprietary.security.database.repository;
import java.util.Date; import java.time.Instant;
import java.util.List; import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
@ -27,7 +27,7 @@ public interface SessionRepository extends JpaRepository<SessionEntity, String>
"UPDATE SessionEntity s SET s.expired = :expired, s.lastRequest = :lastRequest WHERE s.principalName = :principalName") "UPDATE SessionEntity s SET s.expired = :expired, s.lastRequest = :lastRequest WHERE s.principalName = :principalName")
void saveByPrincipalName( void saveByPrincipalName(
@Param("expired") boolean expired, @Param("expired") boolean expired,
@Param("lastRequest") Date lastRequest, @Param("lastRequest") Instant lastRequest,
@Param("principalName") String principalName); @Param("principalName") String principalName);
@Query( @Query(

View File

@ -1,8 +1,9 @@
package stirling.software.proprietary.security.filter; package stirling.software.proprietary.security.filter;
import java.io.IOException; import java.io.IOException;
import java.text.SimpleDateFormat; import java.time.Instant;
import java.util.Date; import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Optional; import java.util.Optional;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
@ -60,8 +61,12 @@ public class FirstLoginFilter extends OncePerRequestFilter {
} }
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
HttpSession session = request.getSession(true); HttpSession session = request.getSession(true);
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss"); DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("HH:mm:ss");
String creationTime = timeFormat.format(new Date(session.getCreationTime())); String creationTime =
timeFormat.format(
Instant.ofEpochMilli(session.getCreationTime())
.atZone(ZoneId.systemDefault())
.toLocalTime());
log.debug( log.debug(
"Request Info - New: {}, creationTimeSession {}, ID: {}, IP: {}, User-Agent: {}, Referer: {}, Request URL: {}", "Request Info - New: {}, creationTimeSession {}, ID: {}, IP: {}, User-Agent: {}, Referer: {}, Request URL: {}",
session.isNew(), session.isNew(),

View File

@ -1,6 +1,6 @@
package stirling.software.proprietary.security.model; package stirling.software.proprietary.security.model;
import java.util.Date; import java.time.Instant;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
@ -25,5 +25,5 @@ public class PersistentLogin {
private String token; private String token;
@Column(name = "last_used", nullable = false) @Column(name = "last_used", nullable = false)
private Date lastUsed; private Instant lastUsed;
} }

View File

@ -1,7 +1,7 @@
package stirling.software.proprietary.security.model; package stirling.software.proprietary.security.model;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.time.Instant;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.Id; import jakarta.persistence.Id;
@ -17,7 +17,7 @@ public class SessionEntity implements Serializable {
private String principalName; private String principalName;
private Date lastRequest; private Instant lastRequest;
private boolean expired; private boolean expired;
} }

View File

@ -4,6 +4,7 @@ import java.security.KeyPair;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import java.time.Instant;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
@ -92,8 +93,8 @@ public class JwtService implements JwtServiceInterface {
.claims(claims) .claims(claims)
.subject(username) .subject(username)
.issuer(ISSUER) .issuer(ISSUER)
.issuedAt(new Date()) .issuedAt(Date.from(Instant.now()))
.expiration(new Date(System.currentTimeMillis() + EXPIRATION)) .expiration(Date.from(Instant.now().plusMillis(EXPIRATION)))
.signWith(keyPair.getPrivate(), Jwts.SIG.RS256); .signWith(keyPair.getPrivate(), Jwts.SIG.RS256);
String keyId = activeKey.getKeyId(); String keyId = activeKey.getKeyId();
@ -129,7 +130,7 @@ public class JwtService implements JwtServiceInterface {
@Override @Override
public boolean isTokenExpired(String token) { public boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date()); return extractExpiration(token).before(Date.from(Instant.now()));
} }
private Date extractExpiration(String token) { private Date extractExpiration(String token) {

View File

@ -1,6 +1,7 @@
package stirling.software.proprietary.security.session; package stirling.software.proprietary.security.session;
import java.time.Duration; import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
@ -67,7 +68,7 @@ public class SessionPersistentRegistry implements SessionRegistry {
new SessionInformation( new SessionInformation(
sessionEntity.getPrincipalName(), sessionEntity.getPrincipalName(),
sessionEntity.getSessionId(), sessionEntity.getSessionId(),
sessionEntity.getLastRequest())); Date.from(sessionEntity.getLastRequest())));
} }
} }
} }
@ -101,7 +102,7 @@ public class SessionPersistentRegistry implements SessionRegistry {
SessionEntity sessionEntity = new SessionEntity(); SessionEntity sessionEntity = new SessionEntity();
sessionEntity.setSessionId(sessionId); sessionEntity.setSessionId(sessionId);
sessionEntity.setPrincipalName(principalName); sessionEntity.setPrincipalName(principalName);
sessionEntity.setLastRequest(new Date()); // Set lastRequest to the current date sessionEntity.setLastRequest(Instant.now()); // Set lastRequest to the current date
sessionEntity.setExpired(false); sessionEntity.setExpired(false);
sessionRepository.save(sessionEntity); sessionRepository.save(sessionEntity);
} }
@ -119,7 +120,7 @@ public class SessionPersistentRegistry implements SessionRegistry {
Optional<SessionEntity> sessionEntityOpt = sessionRepository.findById(sessionId); Optional<SessionEntity> sessionEntityOpt = sessionRepository.findById(sessionId);
if (sessionEntityOpt.isPresent()) { if (sessionEntityOpt.isPresent()) {
SessionEntity sessionEntity = sessionEntityOpt.get(); SessionEntity sessionEntity = sessionEntityOpt.get();
sessionEntity.setLastRequest(new Date()); // Update lastRequest to the current date sessionEntity.setLastRequest(Instant.now()); // Update lastRequest to the current date
sessionRepository.save(sessionEntity); sessionRepository.save(sessionEntity);
} }
} }
@ -132,7 +133,7 @@ public class SessionPersistentRegistry implements SessionRegistry {
return new SessionInformation( return new SessionInformation(
sessionEntity.getPrincipalName(), sessionEntity.getPrincipalName(),
sessionEntity.getSessionId(), sessionEntity.getSessionId(),
sessionEntity.getLastRequest()); Date.from(sessionEntity.getLastRequest()));
} }
return null; return null;
} }
@ -170,7 +171,7 @@ public class SessionPersistentRegistry implements SessionRegistry {
// Update session details by principal name // Update session details by principal name
public void updateSessionByPrincipalName( public void updateSessionByPrincipalName(
String principalName, boolean expired, Date lastRequest) { String principalName, boolean expired, Date lastRequest) {
sessionRepository.saveByPrincipalName(expired, lastRequest, principalName); sessionRepository.saveByPrincipalName(expired, lastRequest.toInstant(), principalName);
} }
// Find the latest session for a given principal name // Find the latest session for a given principal name