From 4a7fdb16f3b9b4d6989682203668503a09a3e84c Mon Sep 17 00:00:00 2001 From: Gyeongtae Park <67095975+ParkGyeongTae@users.noreply.github.com> Date: Tue, 12 Nov 2024 19:09:42 +0900 Subject: [PATCH] [ZEPPELIN-6131] Update Interpreter to Store Values as Integers When Applicable ### What is this PR for? There was an issue where the port was treated as a string and included `.0` when modifying the port of the Elasticsearch interpreter. ### What type of PR is it? Bug Fix ### Todos * [x] - Modified HttpBasedClient.java file ### What is the Jira issue? * https://issues.apache.org/jira/projects/ZEPPELIN/issues/ZEPPELIN-6131 ### How should this be tested? * Zeppelin project build and run ```bash ./mvnw clean package -DskipTests ``` * Add a test index and test data to Elasticsearch ```bash # Create an index curl -X PUT "http://localhost:9200/customer?pretty" # Create a document curl -X \ PUT "localhost:9200/customer/_doc/1" \ -H 'Content-Type: application/json' \ -d' { "field_1": "value_1" }' ``` * Modify the Elasticsearch interpreter type and port elasticsearch.client.type: transport -> http elasticsearch.port: 9300 -> 9200 * Previous configuration image * New configuration image * Create a Zeppelin notebook and execute a paragraph ```bash %elasticsearch get /customer/_doc/1 ``` * Result before modification - Error occurred image ```bash org.apache.zeppelin.interpreter.InterpreterException: java.lang.NumberFormatException: For input string: "9200.0" at org.apache.zeppelin.interpreter.LazyOpenInterpreter.open(LazyOpenInterpreter.java:77) at org.apache.zeppelin.interpreter.remote.RemoteInterpreterServer$InterpretJob.jobRun(RemoteInterpreterServer.java:808) at org.apache.zeppelin.interpreter.remote.RemoteInterpreterServer$InterpretJob.jobRun(RemoteInterpreterServer.java:716) at org.apache.zeppelin.scheduler.Job.run(Job.java:187) at org.apache.zeppelin.scheduler.AbstractScheduler.runJob(AbstractScheduler.java:136) at org.apache.zeppelin.scheduler.FIFOScheduler.lambda$runJobInScheduler$0(FIFOScheduler.java:42) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) at java.base/java.lang.Thread.run(Thread.java:829) Caused by: java.lang.NumberFormatException: For input string: "9200.0" at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.base/java.lang.Integer.parseInt(Integer.java:652) at java.base/java.lang.Integer.parseInt(Integer.java:770) at org.apache.zeppelin.elasticsearch.client.HttpBasedClient.(HttpBasedClient.java:67) at org.apache.zeppelin.elasticsearch.ElasticsearchInterpreter.open(ElasticsearchInterpreter.java:136) at org.apache.zeppelin.interpreter.LazyOpenInterpreter.open(LazyOpenInterpreter.java:71) ... 8 more ``` * Result after modification image ### Screenshots (if appropriate) * Refer to the above content ### Questions: * Does the license files need to update? No. * Is there breaking changes for older versions? No. * Does this needs documentation? No. Closes #4878 from ParkGyeongTae/fix-elasticsearch-port-string-handling. Signed-off-by: Philipp Dallig --- .../UpdateInterpreterSettingRequest.java | 45 +++++++ .../UpdateInterpreterSettingRequestTest.java | 115 ++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 zeppelin-server/src/test/java/org/apache/zeppelin/rest/message/UpdateInterpreterSettingRequestTest.java diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/UpdateInterpreterSettingRequest.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/UpdateInterpreterSettingRequest.java index 79cc1e19b56..ca7e76a0b9e 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/UpdateInterpreterSettingRequest.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/message/UpdateInterpreterSettingRequest.java @@ -39,10 +39,55 @@ public UpdateInterpreterSettingRequest(Map properti this.option = option; } + /** + * Retrieves the properties of the interpreter and, if the property type is "number" + * and its value is a double that represents a whole number, converts that value to an integer. + * + * @return A map of the interpreter's properties with possible modifications to the numeric properties. + */ public Map getProperties() { + properties.forEach((key, property) -> { + if (isNumberType(property) && isWholeNumberDouble(property.getValue())) { + convertDoubleToInt(property); + } + }); return properties; } + /** + * Checks if the property type is "number". + * + * @param property The InterpreterProperty to check. + * @return true if the property type is "number", false otherwise. + */ + private boolean isNumberType(InterpreterProperty property) { + return "number".equals(property.getType()); + } + + /** + * Checks if the given value is a Double and represents a whole number. + * + * @param value The object to check. + * @return true if the value is a Double and a whole number, false otherwise. + */ + private boolean isWholeNumberDouble(Object value) { + if (value instanceof Double) { + Double doubleValue = (Double) value; + return doubleValue == Math.floor(doubleValue); + } + return false; + } + + /** + * Converts the value of the given property from a Double to an Integer if the Double represents a whole number. + * + * @param property The InterpreterProperty whose value will be converted. + */ + private void convertDoubleToInt(InterpreterProperty property) { + Double doubleValue = (Double) property.getValue(); + property.setValue(doubleValue.intValue()); + } + public List getDependencies() { return dependencies; } diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/message/UpdateInterpreterSettingRequestTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/message/UpdateInterpreterSettingRequestTest.java new file mode 100644 index 00000000000..1ecf555ad7e --- /dev/null +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/message/UpdateInterpreterSettingRequestTest.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.zeppelin.rest.message; + +import org.apache.zeppelin.dep.Dependency; +import org.apache.zeppelin.interpreter.InterpreterOption; +import org.apache.zeppelin.interpreter.InterpreterProperty; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Unit test class for the UpdateInterpreterSettingRequest. + * This class tests the behavior of UpdateInterpreterSettingRequest methods, + * especially focusing on property type handling and value conversions. + */ +class UpdateInterpreterSettingRequestTest { + + private Map properties; + private List dependencies; + private InterpreterOption option; + private UpdateInterpreterSettingRequest request; + + /** + * Setup method that initializes test fixtures before each test. + * Mocks dependencies and option objects to isolate UpdateInterpreterSettingRequest behavior. + */ + @BeforeEach + void setUp() { + properties = new HashMap<>(); + dependencies = Collections.emptyList(); + option = mock(InterpreterOption.class); + request = new UpdateInterpreterSettingRequest(properties, dependencies, option); + } + + /** + * Tests getProperties method to verify that properties with "number" type + * and whole-number Double values are correctly converted to Integer. + * Verifies that only whole-number Doubles are converted and non-integer Doubles remain unchanged. + */ + @Test + void testGetPropertiesWithWholeNumberDoubleConversion() { + InterpreterProperty property1 = mock(InterpreterProperty.class); + when(property1.getType()).thenReturn("number"); + when(property1.getValue()).thenReturn(5.0); + + InterpreterProperty property2 = mock(InterpreterProperty.class); + when(property2.getType()).thenReturn("number"); + when(property2.getValue()).thenReturn(5.5); + + properties.put("property1", property1); + properties.put("property2", property2); + + Map resultProperties = request.getProperties(); + + verify(property1).setValue(5); + verify(property2, never()).setValue(any()); + assertEquals(properties, resultProperties); + } + + /** + * Tests getProperties method when the property type is not "number". + * Verifies that no conversion is performed on non-number types. + */ + @Test + void testGetPropertiesWithoutConversion() { + InterpreterProperty property = mock(InterpreterProperty.class); + when(property.getType()).thenReturn("string"); + when(property.getValue()).thenReturn("test"); + + properties.put("property", property); + + Map resultProperties = request.getProperties(); + + verify(property, never()).setValue(any()); + assertEquals(properties, resultProperties); + } + + /** + * Tests getDependencies method to confirm that it returns the correct dependencies list. + */ + @Test + void testGetDependencies() { + assertEquals(dependencies, request.getDependencies()); + } + + /** + * Tests getOption method to confirm that it returns the correct interpreter option. + */ + @Test + void testGetOption() { + assertEquals(option, request.getOption()); + } +}