parse timestamps as ZonedDateTime internally
All checks were successful
continuous-integration/drone/push Build is passing

Timestamps have been stored with their String representation from the
API with convenience methods to convert them into ZonedDateTime.
We now use the Jackson JavaTimeModule to parse them directly and swap
the real and convenience getters.
This commit is contained in:
Stefan Kalscheuer 2023-06-15 17:50:47 +02:00
parent b72298f2a8
commit 7e5d193d1b
Signed by: stefan
GPG Key ID: 3887EC2A53B55430
11 changed files with 110 additions and 79 deletions

View File

@ -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)

View File

@ -4,7 +4,7 @@
<groupId>de.stklcode.jvault</groupId>
<artifactId>jvault-connector</artifactId>
<version>1.1.5-SNAPSHOT</version>
<version>1.2.0-SNAPSHOT</version>
<packaging>jar</packaging>
@ -51,6 +51,11 @@
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.15.2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
@ -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
</argLine>
</configuration>
</plugin>

View File

@ -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);
}
/**

View File

@ -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 {

View File

@ -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<Integer, VersionMetadata> 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);
}
}

View File

@ -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<String, Object> 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;
}
/**

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<T> {
*/
protected AbstractModelTest(Class<T> 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);
}
/**

View File

@ -109,7 +109,7 @@ class MetaSecretResponseTest extends AbstractModelTest<MetaSecretResponse> {
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");