diff --git a/file/src/main/java/org/apache/zeppelin/file/HDFSFileInterpreter.java b/file/src/main/java/org/apache/zeppelin/file/HDFSFileInterpreter.java index 3429de0b55a..662d02add81 100644 --- a/file/src/main/java/org/apache/zeppelin/file/HDFSFileInterpreter.java +++ b/file/src/main/java/org/apache/zeppelin/file/HDFSFileInterpreter.java @@ -28,6 +28,7 @@ import java.util.Date; import java.util.List; import java.util.Properties; +import java.util.TimeZone; import org.apache.zeppelin.completer.CompletionType; import org.apache.zeppelin.interpreter.InterpreterContext; @@ -173,7 +174,11 @@ private String listPermission(OneFileStatus fs){ } private String listDate(OneFileStatus fs) { - return new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date(fs.modificationTime)); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + // Format in GMT so the value matches the "GMT" label appended in listOne(), + // regardless of the JVM default time zone. + sdf.setTimeZone(TimeZone.getTimeZone("GMT")); + return sdf.format(new Date(fs.modificationTime)); } private String listOne(String path, OneFileStatus fs) { diff --git a/file/src/test/java/org/apache/zeppelin/file/HDFSFileInterpreterTest.java b/file/src/test/java/org/apache/zeppelin/file/HDFSFileInterpreterTest.java index 5c9e268e5d4..dc4dcbe5cc8 100644 --- a/file/src/test/java/org/apache/zeppelin/file/HDFSFileInterpreterTest.java +++ b/file/src/test/java/org/apache/zeppelin/file/HDFSFileInterpreterTest.java @@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.google.gson.Gson; @@ -30,6 +31,7 @@ import java.util.HashMap; import java.util.List; import java.util.Properties; +import java.util.TimeZone; import org.apache.zeppelin.completer.CompletionType; import org.apache.zeppelin.interpreter.InterpreterResult; @@ -183,6 +185,26 @@ void testNoSuchFile() { t.close(); } + @Test + void testListDateFormatsInGmtToMatchLabel() { + // ZEPPELIN-6483: the timestamp must be formatted in GMT to match the trailing + // "GMT" label, regardless of the JVM default time zone. + TimeZone original = TimeZone.getDefault(); + try { + TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul")); + HDFSFileInterpreter t = new MockHDFSFileInterpreter(new Properties()); + t.open(); + InterpreterResult result = t.interpret("ls -l /", null); + String out = result.message().get(0).getData(); + // modificationTime 1438548219672 == 2015-08-02 20:43 GMT (2015-08-03 05:43 in KST) + assertTrue(out.contains("2015-08-02 20:43GMT"), + "modification time should be shown in GMT to match the label, but was:\n" + out); + t.close(); + } finally { + TimeZone.setDefault(original); + } + } + } /** diff --git a/mongodb/src/main/java/org/apache/zeppelin/mongodb/MongoDbInterpreter.java b/mongodb/src/main/java/org/apache/zeppelin/mongodb/MongoDbInterpreter.java index 54c121fcdff..5521135a391 100644 --- a/mongodb/src/main/java/org/apache/zeppelin/mongodb/MongoDbInterpreter.java +++ b/mongodb/src/main/java/org/apache/zeppelin/mongodb/MongoDbInterpreter.java @@ -35,6 +35,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.zeppelin.interpreter.Interpreter; import org.apache.zeppelin.interpreter.InterpreterContext; +import org.apache.zeppelin.interpreter.InterpreterException; import org.apache.zeppelin.interpreter.InterpreterResult; import org.apache.zeppelin.interpreter.InterpreterResult.Code; import org.apache.zeppelin.scheduler.Scheduler; @@ -66,13 +67,28 @@ public MongoDbInterpreter(Properties property) { } @Override - public void open() { + public void open() throws InterpreterException { try (final Scanner scanner = new Scanner(MongoDbInterpreter.class.getResourceAsStream("/shell_extension.js"), "UTF-8").useDelimiter("\\A")) { shellExtension = scanner.next(); } - commandTimeout = Long.parseLong(getProperty("mongo.shell.command.timeout")); - maxConcurrency = Integer.parseInt(getProperty("mongo.interpreter.concurrency.max")); + + final String commandTimeoutValue = getProperty("mongo.shell.command.timeout"); + try { + commandTimeout = Long.parseLong(commandTimeoutValue); + } catch (NumberFormatException e) { + throw new InterpreterException("Invalid value for property " + + "'mongo.shell.command.timeout': " + commandTimeoutValue, e); + } + + final String maxConcurrencyValue = getProperty("mongo.interpreter.concurrency.max"); + try { + maxConcurrency = Integer.parseInt(maxConcurrencyValue); + } catch (NumberFormatException e) { + throw new InterpreterException("Invalid value for property " + + "'mongo.interpreter.concurrency.max': " + maxConcurrencyValue, e); + } + dbAddress = getProperty("mongo.server.host") + ":" + getProperty("mongo.server.port"); prepareShellExtension(); } diff --git a/mongodb/src/test/java/org/apache/zeppelin/mongodb/MongoDbInterpreterTest.java b/mongodb/src/test/java/org/apache/zeppelin/mongodb/MongoDbInterpreterTest.java index 08991c3a844..cd57db0a96a 100644 --- a/mongodb/src/test/java/org/apache/zeppelin/mongodb/MongoDbInterpreterTest.java +++ b/mongodb/src/test/java/org/apache/zeppelin/mongodb/MongoDbInterpreterTest.java @@ -19,6 +19,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; import java.io.IOException; @@ -30,6 +32,7 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.apache.zeppelin.interpreter.InterpreterContext; +import org.apache.zeppelin.interpreter.InterpreterException; import org.apache.zeppelin.interpreter.InterpreterOutput; import org.apache.zeppelin.interpreter.InterpreterOutputListener; import org.apache.zeppelin.interpreter.InterpreterResult; @@ -80,7 +83,7 @@ public static void setup() { } @BeforeEach - public void init() { + public void init() throws InterpreterException { buffer = ByteBuffer.allocate(10000); props.put("mongo.shell.path", (IS_WINDOWS ? "" : "sh ") + MONGO_SHELL); props.put("mongo.shell.command.table.limit", "10000"); @@ -137,6 +140,60 @@ void testBadConf() { assertSame(Code.ERROR, res.code()); } + @Test + void testInvalidCommandTimeout() { + props.setProperty("mongo.shell.command.timeout", "not-a-number"); + + final InterpreterException e = + assertThrows(InterpreterException.class, () -> interpreter.open()); + + assertTrue(e.getMessage().contains("mongo.shell.command.timeout"), + "The message must name the offending property: " + e.getMessage()); + assertTrue(e.getMessage().contains("not-a-number"), + "The message must show the invalid value: " + e.getMessage()); + assertTrue(e.getCause() instanceof NumberFormatException, + "The original NumberFormatException must be preserved as the cause"); + } + + @Test + void testMissingCommandTimeout() { + props.remove("mongo.shell.command.timeout"); + + final InterpreterException e = + assertThrows(InterpreterException.class, () -> interpreter.open()); + + assertTrue(e.getMessage().contains("mongo.shell.command.timeout"), + "The message must name the offending property: " + e.getMessage()); + assertTrue(e.getCause() instanceof NumberFormatException, + "The original NumberFormatException must be preserved as the cause"); + } + + @Test + void testEmptyMaxConcurrency() { + props.setProperty("mongo.interpreter.concurrency.max", ""); + + final InterpreterException e = + assertThrows(InterpreterException.class, () -> interpreter.open()); + + assertTrue(e.getMessage().contains("mongo.interpreter.concurrency.max"), + "The message must name the offending property: " + e.getMessage()); + assertTrue(e.getCause() instanceof NumberFormatException, + "The original NumberFormatException must be preserved as the cause"); + } + + @Test + void testMissingMaxConcurrency() { + props.remove("mongo.interpreter.concurrency.max"); + + final InterpreterException e = + assertThrows(InterpreterException.class, () -> interpreter.open()); + + assertTrue(e.getMessage().contains("mongo.interpreter.concurrency.max"), + "The message must name the offending property: " + e.getMessage()); + assertTrue(e.getCause() instanceof NumberFormatException, + "The original NumberFormatException must be preserved as the cause"); + } + @Override public void onUpdateAll(InterpreterOutput interpreterOutput) {