summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlice Kober-Sotzek <aliceks@google.com>2019-07-25 18:50:20 +0200
committerAlice Kober-Sotzek <aliceks@google.com>2019-07-26 14:05:30 +0200
commit03b56b421a1a0ea25f3f6b80bc1d0d08c79949ad (patch)
tree7d329de6fd095fd1742f2f1d4e4c24ddab3a691e
parent2a5dfa802973b1cd50f879eeb3caa8c38866577f (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
-rw-r--r--java/com/google/gerrit/json/SqlTimestampDeserializer.java10
-rw-r--r--java/com/google/gerrit/server/util/time/BUILD1
-rw-r--r--java/com/google/gerrit/server/util/time/TimeUtil.java13
-rw-r--r--javatests/com/google/gerrit/json/BUILD2
-rw-r--r--javatests/com/google/gerrit/json/SqlTimestampDeserializerTest.java33
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());
+ }
+}