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
This commit is contained in:
Alice Kober-Sotzek
2019-07-25 18:50:20 +02:00
parent 2a5dfa8029
commit 03b56b421a
5 changed files with 58 additions and 1 deletions

View File

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

View File

@@ -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",
],

View File

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

View File

@@ -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",
],

View File

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