diff options
author | Alice Kober-Sotzek <aliceks@google.com> | 2019-07-25 18:50:20 +0200 |
---|---|---|
committer | Alice Kober-Sotzek <aliceks@google.com> | 2019-07-26 14:05:30 +0200 |
commit | 03b56b421a1a0ea25f3f6b80bc1d0d08c79949ad (patch) | |
tree | 7d329de6fd095fd1742f2f1d4e4c24ddab3a691e | |
parent | 2a5dfa802973b1cd50f879eeb3caa8c38866577f (diff) |
Introduce 'null object' timestamp and also parse it from JSON
In Gerrit, it is common practice to use empty Strings to unset a value
in contexts where using null is not possible. We need something similar
for timestamps as well.
Inside of the Gerrit backend, we introduce TimeUtil.never() to indicate
this value. For users of Gerrit's REST API, we introduce another mapping
for convenience: an empty timestamp string in JSON will be mapped to the
magic value. We deliberately don't add the reverse mapping as proper
implementations in Gerrit should never set this value internally and
hence it should never be returned to the user.
Change-Id: I533b793227de4315893ee99750b20a442ff3c081
5 files changed, 58 insertions, 1 deletions
diff --git a/java/com/google/gerrit/json/SqlTimestampDeserializer.java b/java/com/google/gerrit/json/SqlTimestampDeserializer.java index 0148226284..e1cf38210a 100644 --- a/java/com/google/gerrit/json/SqlTimestampDeserializer.java +++ b/java/com/google/gerrit/json/SqlTimestampDeserializer.java @@ -44,7 +44,15 @@ class SqlTimestampDeserializer implements JsonDeserializer<Timestamp>, JsonSeria throw new JsonParseException("Expected string for timestamp type"); } - return JavaSqlTimestampHelper.parseTimestamp(p.getAsString()); + String input = p.getAsString(); + if (input.trim().isEmpty()) { + // Magic timestamp to indicate no timestamp. (-> null object) + // Always create a new object as timestamps are mutable. Don't use TimeUtil.never() to not + // introduce an undesired dependency. + return new Timestamp(0); + } + + return JavaSqlTimestampHelper.parseTimestamp(input); } @Override diff --git a/java/com/google/gerrit/server/util/time/BUILD b/java/com/google/gerrit/server/util/time/BUILD index 1d1305d266..c7cd89ec78 100644 --- a/java/com/google/gerrit/server/util/time/BUILD +++ b/java/com/google/gerrit/server/util/time/BUILD @@ -3,6 +3,7 @@ java_library( srcs = glob(["**/*.java"]), visibility = ["//visibility:public"], deps = [ + "//java/com/google/gerrit/common:annotations", "//lib:guava", "//lib/jgit/org.eclipse.jgit:jgit", ], diff --git a/java/com/google/gerrit/server/util/time/TimeUtil.java b/java/com/google/gerrit/server/util/time/TimeUtil.java index 645dbb92f8..b9d2d8af98 100644 --- a/java/com/google/gerrit/server/util/time/TimeUtil.java +++ b/java/com/google/gerrit/server/util/time/TimeUtil.java @@ -15,6 +15,8 @@ package com.google.gerrit.server.util.time; import com.google.common.annotations.VisibleForTesting; +import com.google.gerrit.common.UsedAt; +import com.google.gerrit.common.UsedAt.Project; import java.sql.Timestamp; import java.time.Instant; import java.util.function.LongSupplier; @@ -43,6 +45,17 @@ public class TimeUtil { return new Timestamp(nowMs()); } + /** + * Returns the magic timestamp representing no specific time. + * + * <p>This "null object" is helpful in contexts where using {@code null} directly is not possible. + */ + @UsedAt(Project.PLUGIN_CHECKS) + public static Timestamp never() { + // Always create a new object as timestamps are mutable. + return new Timestamp(0); + } + public static Timestamp truncateToSecond(Timestamp t) { return new Timestamp((t.getTime() / 1000) * 1000); } diff --git a/javatests/com/google/gerrit/json/BUILD b/javatests/com/google/gerrit/json/BUILD index 2d95652632..575f575bd2 100644 --- a/javatests/com/google/gerrit/json/BUILD +++ b/javatests/com/google/gerrit/json/BUILD @@ -5,7 +5,9 @@ junit_tests( srcs = glob(["*.java"]), deps = [ "//java/com/google/gerrit/json", + "//java/com/google/gerrit/server/util/time", "//java/com/google/gerrit/testing:gerrit-test-util", + "//lib:gson", "//lib:guava", "//lib/truth", ], diff --git a/javatests/com/google/gerrit/json/SqlTimestampDeserializerTest.java b/javatests/com/google/gerrit/json/SqlTimestampDeserializerTest.java new file mode 100644 index 0000000000..2699c3b793 --- /dev/null +++ b/javatests/com/google/gerrit/json/SqlTimestampDeserializerTest.java @@ -0,0 +1,33 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.json; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.gerrit.server.util.time.TimeUtil; +import com.google.gson.JsonPrimitive; +import java.sql.Timestamp; +import org.junit.Test; + +public class SqlTimestampDeserializerTest { + + private final SqlTimestampDeserializer deserializer = new SqlTimestampDeserializer(); + + @Test + public void emptyStringIsDeserializedToMagicTimestamp() { + Timestamp timestamp = deserializer.deserialize(new JsonPrimitive(""), Timestamp.class, null); + assertThat(timestamp).isEqualTo(TimeUtil.never()); + } +} |