diff --git a/CHANGELOG.md b/CHANGELOG.md index de32017..150eeb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,14 @@ ## unreleased +### Deprecations +* `get...TimeString()` methods on various model classes are now deprecated + ### Fix * Fixed JSON type conversion in `SecretResponse#get(String, Class)` (#67) +### Improvements +* Parse timestamps as `ZonedDateTime` instead of `String` representation + ## 1.1.4 (2023-06-15) diff --git a/pom.xml b/pom.xml index 458a141..e4fd371 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ de.stklcode.jvault jvault-connector - 1.1.5-SNAPSHOT + 1.2.0-SNAPSHOT jar @@ -51,6 +51,11 @@ jackson-databind 2.15.2 + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.15.2 + org.junit.jupiter @@ -170,6 +175,7 @@ --add-opens de.stklcode.jvault.connector/de.stklcode.jvault.connector.model.response=ALL-UNNAMED --add-opens de.stklcode.jvault.connector/de.stklcode.jvault.connector.model.response.embedded=ALL-UNNAMED --add-opens de.stklcode.jvault.connector/de.stklcode.jvault.connector.test=com.fasterxml.jackson.databind + --add-opens de.stklcode.jvault.connector/de.stklcode.jvault.connector.test=com.fasterxml.jackson.datatype.jsr310 diff --git a/src/main/java/de/stklcode/jvault/connector/internal/RequestHelper.java b/src/main/java/de/stklcode/jvault/connector/internal/RequestHelper.java index 724640f..ec047dc 100644 --- a/src/main/java/de/stklcode/jvault/connector/internal/RequestHelper.java +++ b/src/main/java/de/stklcode/jvault/connector/internal/RequestHelper.java @@ -1,7 +1,10 @@ package de.stklcode.jvault.connector.internal; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import de.stklcode.jvault.connector.exception.*; import de.stklcode.jvault.connector.model.response.ErrorResponse; @@ -62,7 +65,10 @@ public final class RequestHelper implements Serializable { this.timeout = timeout; this.tlsVersion = tlsVersion; this.trustedCaCert = trustedCaCert; - this.jsonMapper = new ObjectMapper(); + this.jsonMapper = new ObjectMapper() + .registerModule(new JavaTimeModule()) + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE); } /** diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/SecretResponse.java b/src/main/java/de/stklcode/jvault/connector/model/response/SecretResponse.java index c50a301..b256371 100644 --- a/src/main/java/de/stklcode/jvault/connector/model/response/SecretResponse.java +++ b/src/main/java/de/stklcode/jvault/connector/model/response/SecretResponse.java @@ -17,7 +17,10 @@ package de.stklcode.jvault.connector.model.response; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import de.stklcode.jvault.connector.exception.InvalidResponseException; import de.stklcode.jvault.connector.model.response.embedded.VersionMetadata; @@ -82,7 +85,11 @@ public abstract class SecretResponse extends VaultDataResponse { } else if (type.isInstance(rawValue)) { return type.cast(rawValue); } else { - var om = new ObjectMapper(); + var om = new ObjectMapper() + .registerModule(new JavaTimeModule()) + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE); + if (rawValue instanceof String) { return om.readValue((String) rawValue, type); } else { diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/embedded/SecretMetadata.java b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/SecretMetadata.java index 2773bcb..503aaad 100644 --- a/src/main/java/de/stklcode/jvault/connector/model/response/embedded/SecretMetadata.java +++ b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/SecretMetadata.java @@ -22,7 +22,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import java.io.Serializable; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; import java.util.Map; import java.util.Objects; @@ -35,13 +34,13 @@ import java.util.Objects; */ @JsonIgnoreProperties(ignoreUnknown = true) public final class SecretMetadata implements Serializable { - private static final long serialVersionUID = 1684891108903409038L; + private static final long serialVersionUID = -4967896264361344676L; private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSSXXX"); @JsonProperty("created_time") - private String createdTimeString; + private ZonedDateTime createdTime; @JsonProperty("current_version") private Integer currentVersion; @@ -53,31 +52,29 @@ public final class SecretMetadata implements Serializable { private Integer oldestVersion; @JsonProperty("updated_time") - private String updatedTime; + private ZonedDateTime updatedTime; @JsonProperty("versions") private Map versions; /** * @return Time of secret creation as raw string representation. + * @deprecated Method left for backwards compatibility only. Use {@link #getCreatedTime()} instead. */ + @Deprecated(since = "1.2", forRemoval = true) public String getCreatedTimeString() { - return createdTimeString; + if (createdTime != null) { + return TIME_FORMAT.format(createdTime); + } + + return null; } /** * @return Time of secret creation. */ public ZonedDateTime getCreatedTime() { - if (createdTimeString != null && !createdTimeString.isEmpty()) { - try { - return ZonedDateTime.parse(createdTimeString, TIME_FORMAT); - } catch (DateTimeParseException e) { - // Ignore. - } - } - - return null; + return createdTime; } /** @@ -103,26 +100,24 @@ public final class SecretMetadata implements Serializable { /** * @return Time of secret update as raw string representation. + * @deprecated Method left for backwards compatibility only. Use {@link #getUpdatedTime()} instead. */ + @Deprecated(since = "1.2", forRemoval = true) public String getUpdatedTimeString() { - return updatedTime; - } - - /** - * @return Time of secret update.. - */ - public ZonedDateTime getUpdatedTime() { - if (updatedTime != null && !updatedTime.isEmpty()) { - try { - return ZonedDateTime.parse(updatedTime, TIME_FORMAT); - } catch (DateTimeParseException e) { - // Ignore. - } + if (updatedTime != null) { + return TIME_FORMAT.format(updatedTime); } return null; } + /** + * @return Time of secret update. + */ + public ZonedDateTime getUpdatedTime() { + return updatedTime; + } + /** * @return Version of the entry. */ @@ -138,7 +133,7 @@ public final class SecretMetadata implements Serializable { return false; } SecretMetadata that = (SecretMetadata) o; - return Objects.equals(createdTimeString, that.createdTimeString) && + return Objects.equals(createdTime, that.createdTime) && Objects.equals(currentVersion, that.currentVersion) && Objects.equals(maxVersions, that.maxVersions) && Objects.equals(oldestVersion, that.oldestVersion) && @@ -148,6 +143,6 @@ public final class SecretMetadata implements Serializable { @Override public int hashCode() { - return Objects.hash(createdTimeString, currentVersion, maxVersions, oldestVersion, updatedTime, versions); + return Objects.hash(createdTime, currentVersion, maxVersions, oldestVersion, updatedTime, versions); } } diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/embedded/TokenData.java b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/TokenData.java index 39d6de1..a7d5fda 100644 --- a/src/main/java/de/stklcode/jvault/connector/model/response/embedded/TokenData.java +++ b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/TokenData.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import java.io.Serializable; import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Map; import java.util.Objects; @@ -34,7 +35,10 @@ import java.util.Objects; */ @JsonIgnoreProperties(ignoreUnknown = true) public final class TokenData implements Serializable { - private static final long serialVersionUID = 2915180734313753649L; + private static final long serialVersionUID = -5749716740973138916L; + + private static final DateTimeFormatter TIME_FORMAT = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSSXXX"); @JsonProperty("accessor") private String accessor; @@ -52,7 +56,7 @@ public final class TokenData implements Serializable { private String entityId; @JsonProperty("expire_time") - private String expireTime; + private ZonedDateTime expireTime; @JsonProperty("explicit_max_ttl") private Integer explicitMaxTtl; @@ -61,7 +65,7 @@ public final class TokenData implements Serializable { private String id; @JsonProperty("issue_time") - private String issueTime; + private ZonedDateTime issueTime; @JsonProperty("meta") private Map meta; @@ -126,9 +130,15 @@ public final class TokenData implements Serializable { /** * @return Expire time as raw string value * @since 0.9 + * @deprecated Method left for backwards compatibility only. Use {@link #getExpireTime()} instead. */ + @Deprecated(since = "1.2", forRemoval = true) public String getExpireTimeString() { - return expireTime; + if (expireTime != null) { + return TIME_FORMAT.format(expireTime); + } + + return null; } /** @@ -136,11 +146,7 @@ public final class TokenData implements Serializable { * @since 0.9 */ public ZonedDateTime getExpireTime() { - if (expireTime == null) { - return null; - } else { - return ZonedDateTime.parse(expireTime); - } + return expireTime; } /** @@ -161,9 +167,15 @@ public final class TokenData implements Serializable { /** * @return Issue time as raw string value * @since 0.9 + * @deprecated Method left for backwards compatibility only. Use {@link #getIssueTime()} instead. */ + @Deprecated(since = "1.2", forRemoval = true) public String getIssueTimeString() { - return issueTime; + if (issueTime != null) { + return TIME_FORMAT.format(issueTime); + } + + return null; } /** @@ -171,11 +183,7 @@ public final class TokenData implements Serializable { * @since 0.9 */ public ZonedDateTime getIssueTime() { - if (issueTime == null) { - return null; - } else { - return ZonedDateTime.parse(issueTime); - } + return issueTime; } /** diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/embedded/VersionMetadata.java b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/VersionMetadata.java index be9f3f9..5f1d3d1 100644 --- a/src/main/java/de/stklcode/jvault/connector/model/response/embedded/VersionMetadata.java +++ b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/VersionMetadata.java @@ -22,7 +22,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import java.io.Serializable; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; import java.util.Objects; /** @@ -34,16 +33,16 @@ import java.util.Objects; */ @JsonIgnoreProperties(ignoreUnknown = true) public final class VersionMetadata implements Serializable { - private static final long serialVersionUID = -5286693953873839611L; + private static final long serialVersionUID = -6815731513868586713L; private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSSXXX"); @JsonProperty("created_time") - private String createdTimeString; + private ZonedDateTime createdTime; @JsonProperty("deletion_time") - private String deletionTimeString; + private ZonedDateTime deletionTime; @JsonProperty("destroyed") private boolean destroyed; @@ -53,46 +52,42 @@ public final class VersionMetadata implements Serializable { /** * @return Time of secret creation as raw string representation. + * @deprecated Method left for backwards compatibility only. Use {@link #getCreatedTime()} instead. */ + @Deprecated(since = "1.2", forRemoval = true) public String getCreatedTimeString() { - return createdTimeString; + if (createdTime != null) { + return TIME_FORMAT.format(createdTime); + } + + return null; } /** * @return Time of secret creation. */ public ZonedDateTime getCreatedTime() { - if (createdTimeString != null && !createdTimeString.isEmpty()) { - try { - return ZonedDateTime.parse(createdTimeString, TIME_FORMAT); - } catch (DateTimeParseException e) { - // Ignore. - } - } - - return null; + return createdTime; } /** * @return Time for secret deletion as raw string representation. + * @deprecated Method left for backwards compatibility only. Use {@link #getDeletionTime()} instead. */ + @Deprecated(since = "1.2", forRemoval = true) public String getDeletionTimeString() { - return deletionTimeString; + if (deletionTime != null) { + return TIME_FORMAT.format(deletionTime); + } + + return null; } /** * @return Time for secret deletion. */ public ZonedDateTime getDeletionTime() { - if (deletionTimeString != null && !deletionTimeString.isEmpty()) { - try { - return ZonedDateTime.parse(deletionTimeString, TIME_FORMAT); - } catch (DateTimeParseException e) { - // Ignore. - } - } - - return null; + return deletionTime; } /** @@ -118,13 +113,13 @@ public final class VersionMetadata implements Serializable { } VersionMetadata that = (VersionMetadata) o; return destroyed == that.destroyed && - Objects.equals(createdTimeString, that.createdTimeString) && - Objects.equals(deletionTimeString, that.deletionTimeString) && + Objects.equals(createdTime, that.createdTime) && + Objects.equals(deletionTime, that.deletionTime) && Objects.equals(version, that.version); } @Override public int hashCode() { - return Objects.hash(createdTimeString, deletionTimeString, destroyed, version); + return Objects.hash(createdTime, deletionTime, destroyed, version); } } diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/embedded/WrapInfo.java b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/WrapInfo.java index d89eee4..64ebeb5 100644 --- a/src/main/java/de/stklcode/jvault/connector/model/response/embedded/WrapInfo.java +++ b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/WrapInfo.java @@ -19,6 +19,7 @@ package de.stklcode.jvault.connector.model.response.embedded; import com.fasterxml.jackson.annotation.JsonProperty; import java.io.Serializable; +import java.time.ZonedDateTime; import java.util.Objects; /** @@ -28,7 +29,7 @@ import java.util.Objects; * @since 1.1 */ public class WrapInfo implements Serializable { - private static final long serialVersionUID = -7764500642913116581L; + private static final long serialVersionUID = 4864973237090355607L; @JsonProperty("token") private String token; @@ -37,7 +38,7 @@ public class WrapInfo implements Serializable { private Integer ttl; @JsonProperty("creation_time") - private String creationTime; + private ZonedDateTime creationTime; @JsonProperty("creation_path") private String creationPath; @@ -59,7 +60,7 @@ public class WrapInfo implements Serializable { /** * @return Creation time */ - public String getCreationTime() { + public ZonedDateTime getCreationTime() { return creationTime; } diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 1067c2a..42648bc 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -33,4 +33,5 @@ module de.stklcode.jvault.connector { requires java.base; requires java.net.http; requires com.fasterxml.jackson.databind; + requires com.fasterxml.jackson.datatype.jsr310; } diff --git a/src/test/java/de/stklcode/jvault/connector/model/AbstractModelTest.java b/src/test/java/de/stklcode/jvault/connector/model/AbstractModelTest.java index 0ea2bed..2974339 100644 --- a/src/test/java/de/stklcode/jvault/connector/model/AbstractModelTest.java +++ b/src/test/java/de/stklcode/jvault/connector/model/AbstractModelTest.java @@ -1,6 +1,9 @@ package de.stklcode.jvault.connector.model; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import nl.jqno.equalsverifier.EqualsVerifier; import org.junit.jupiter.api.Test; @@ -26,7 +29,10 @@ public abstract class AbstractModelTest { */ protected AbstractModelTest(Class modelClass) { this.modelClass = modelClass; - this.objectMapper = new ObjectMapper(); + this.objectMapper = new ObjectMapper() + .registerModule(new JavaTimeModule()) + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE); } /** diff --git a/src/test/java/de/stklcode/jvault/connector/model/response/MetaSecretResponseTest.java b/src/test/java/de/stklcode/jvault/connector/model/response/MetaSecretResponseTest.java index bdc360e..42ffffc 100644 --- a/src/test/java/de/stklcode/jvault/connector/model/response/MetaSecretResponseTest.java +++ b/src/test/java/de/stklcode/jvault/connector/model/response/MetaSecretResponseTest.java @@ -109,7 +109,7 @@ class MetaSecretResponseTest extends AbstractModelTest { assertNotNull(res.getMetadata(), "SecretResponse does not contain metadata"); assertEquals(SECRET_META_CREATED, res.getMetadata().getCreatedTimeString(), "Incorrect creation date string"); assertNotNull(res.getMetadata().getCreatedTime(), "Creation date parsing failed"); - assertEquals("", res.getMetadata().getDeletionTimeString(), "Incorrect deletion date string"); + assertNull(res.getMetadata().getDeletionTimeString(), "Incorrect deletion date string"); assertNull(res.getMetadata().getDeletionTime(), "Incorrect deletion date"); assertFalse(res.getMetadata().isDestroyed(), "Secret destroyed when not expected"); assertEquals(1, res.getMetadata().getVersion(), "Incorrect secret version");