diff --git a/.github/workflows/tck-test.yml b/.github/workflows/tck-test.yml index adedebf7..c3ca7b8e 100644 --- a/.github/workflows/tck-test.yml +++ b/.github/workflows/tck-test.yml @@ -23,24 +23,20 @@ jobs: # with: # command: build # args: --manifest-path test_agent/rust/Cargo.toml - # - name: Set up JDK 17 - # uses: actions/setup-java@v3 - # with: - # java-version: '17' - # distribution: 'temurin' - # cache: maven - #- name: Install dependencies - # run: | - # cd scripts - # python build_up_java_latest.py - # - name: Build up_client_socket_java with Maven - # working-directory: up_client_socket/java - # run: | - # mvn clean install --file pom.xml - # - name: Build java_test_agent with Maven - # working-directory: test_agent/java - # run: | - # mvn clean install --file pom.xml + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + cache: maven + - name: Build up_client_socket_java with Maven + working-directory: up_client_socket/java + run: | + mvn clean install --file pom.xml + - name: Build java_test_agent with Maven + working-directory: test_agent/java + run: | + mvn clean install --file pom.xml - name: Set up Python 3.8.7 uses: actions/setup-python@v3 with: diff --git a/.gitignore b/.gitignore index 2bdd34fe..dc8f8d4f 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,5 @@ MANIFEST **/jar_files/** *.jar **/.DS_Store -**/target \ No newline at end of file +**/target +test_manager/reports/summary/summary.json diff --git a/test_agent/java/pom.xml b/test_agent/java/pom.xml index 7c6ba222..103915fa 100644 --- a/test_agent/java/pom.xml +++ b/test_agent/java/pom.xml @@ -34,6 +34,11 @@ json 20231013 + + commons-cli + commons-cli + 1.8.0 + tck-test-agent-java diff --git a/test_agent/java/src/main/java/org/eclipse/uprotocol/Constants/ActionCommands.java b/test_agent/java/src/main/java/org/eclipse/uprotocol/Constants/ActionCommands.java index 39eb6601..08afd75c 100644 --- a/test_agent/java/src/main/java/org/eclipse/uprotocol/Constants/ActionCommands.java +++ b/test_agent/java/src/main/java/org/eclipse/uprotocol/Constants/ActionCommands.java @@ -19,23 +19,21 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.uprotocol.Constants; + package org.eclipse.uprotocol.Constants; -public class ActionCommands { - public static final String SEND_COMMAND = "send"; - public static final String REGISTER_LISTENER_COMMAND = "registerlistener"; - public static final String UNREGISTER_LISTENER_COMMAND = "unregisterlistener"; - public static final String INVOKE_METHOD_COMMAND = "invokemethod"; - public static final String RESPONSE_ON_RECEIVE = "onreceive"; - public static final String RESPONSE_RPC = "rpcresponse"; - public static final String SERIALIZE_URI = "uri_serialize"; - public static final String DESERIALIZE_URI = "uri_deserialize"; - public static final String VALIDATE_URI = "uri_validate"; - public static final String VALIDATE_UUID = "uuid_validate"; - public static final String SERIALIZE_UUID = "uuid_serialize"; - public static final String DESERIALIZE_UUID = "uuid_deserialize"; - public static final String VALIDATE_UATTRIBUTES = "uattributes_validate"; - public static final String MICRO_SERIALIZE_URI = "micro_serialize_uri"; - public static final String MICRO_DESERIALIZE_URI = "micro_deserialize_uri"; - -} + public class ActionCommands { + public static final String SEND_COMMAND = "send"; + public static final String REGISTER_LISTENER_COMMAND = "registerlistener"; + public static final String UNREGISTER_LISTENER_COMMAND = "unregisterlistener"; + public static final String RESPONSE_ON_RECEIVE = "onreceive"; + public static final String RESPONSE_RPC = "rpcresponse"; + public static final String SERIALIZE_URI = "uri_serialize"; + public static final String DESERIALIZE_URI = "uri_deserialize"; + public static final String VALIDATE_URI = "uri_validate"; + public static final String VALIDATE_UUID = "uuid_validate"; + public static final String SERIALIZE_UUID = "uuid_serialize"; + public static final String DESERIALIZE_UUID = "uuid_deserialize"; + public static final String VALIDATE_UATTRIBUTES = "uattributes_validate"; + public static final String INITIALIZE_TRANSPORT = "initialize_transport"; + + } \ No newline at end of file diff --git a/test_agent/java/src/main/java/org/eclipse/uprotocol/Constants/Constant.java b/test_agent/java/src/main/java/org/eclipse/uprotocol/Constants/Constant.java index 0dd6d0c0..87510237 100644 --- a/test_agent/java/src/main/java/org/eclipse/uprotocol/Constants/Constant.java +++ b/test_agent/java/src/main/java/org/eclipse/uprotocol/Constants/Constant.java @@ -26,7 +26,7 @@ public class Constant { public static final String TEST_MANAGER_IP = "127.0.0.5"; - public static final int TEST_MANAGER_PORT = 12345; + public static final int TEST_MANAGER_PORT = 33333; public static final int BYTES_MSG_LENGTH = 32767; } diff --git a/test_agent/java/src/main/java/org/eclipse/uprotocol/TestAgent.java b/test_agent/java/src/main/java/org/eclipse/uprotocol/TestAgent.java index 8d92f1c9..b9aa6dfa 100644 --- a/test_agent/java/src/main/java/org/eclipse/uprotocol/TestAgent.java +++ b/test_agent/java/src/main/java/org/eclipse/uprotocol/TestAgent.java @@ -21,20 +21,24 @@ package org.eclipse.uprotocol; +import org.apache.commons.cli.*; + + import com.google.gson.Gson; import com.google.protobuf.Any; import com.google.protobuf.Message; import com.google.protobuf.StringValue; +import org.eclipse.uprotocol.communication.UPayload; import org.eclipse.uprotocol.Constants.ActionCommands; import org.eclipse.uprotocol.Constants.Constant; import org.eclipse.uprotocol.transport.UListener; -import org.eclipse.uprotocol.transport.builder.UAttributesBuilder; +import org.eclipse.uprotocol.transport.UTransport; +import org.eclipse.uprotocol.transport.builder.UMessageBuilder; import org.eclipse.uprotocol.transport.validate.UAttributesValidator; -import org.eclipse.uprotocol.uri.serializer.LongUriSerializer; +import org.eclipse.uprotocol.uri.serializer.UriSerializer; import org.eclipse.uprotocol.uri.validator.UriValidator; import org.eclipse.uprotocol.validation.ValidationResult; -import org.eclipse.uprotocol.uuid.serializer.LongUuidSerializer; -import org.eclipse.uprotocol.uri.serializer.MicroUriSerializer; +import org.eclipse.uprotocol.uuid.serializer.UuidSerializer; import org.eclipse.uprotocol.uuid.factory.UuidFactory; import org.eclipse.uprotocol.uuid.factory.UuidUtils; import org.eclipse.uprotocol.uuid.validate.UuidValidator; @@ -44,57 +48,65 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.UnsupportedEncodingException; import java.net.Socket; import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; public class TestAgent { + + private static final Socket clientSocket; - private static final SocketUTransport transport; + private static UTransport transport; private static final Map actionHandlers = new HashMap<>(); private static final Logger logger = Logger.getLogger("JavaTestAgent"); private static final UListener listener = TestAgent::handleOnReceive; private static final Gson gson = new Gson(); + private static final UUri RESPONSE_URI; + private static String transportName; + private static String sdkName; static { actionHandlers.put(ActionCommands.SEND_COMMAND, TestAgent::handleSendCommand); actionHandlers.put(ActionCommands.REGISTER_LISTENER_COMMAND, TestAgent::handleRegisterListenerCommand); actionHandlers.put(ActionCommands.UNREGISTER_LISTENER_COMMAND, TestAgent::handleUnregisterListenerCommand); - actionHandlers.put(ActionCommands.INVOKE_METHOD_COMMAND, TestAgent::handleInvokeMethodCommand); - actionHandlers.put(ActionCommands.SERIALIZE_URI, TestAgent::handleLongSerializeUriCommand); - actionHandlers.put(ActionCommands.DESERIALIZE_URI, TestAgent::handleLongDeserializeUriCommand); + actionHandlers.put(ActionCommands.SERIALIZE_URI, TestAgent::handleSerializeUriCommand); + actionHandlers.put(ActionCommands.DESERIALIZE_URI, TestAgent::handleDeserializeUriCommand); actionHandlers.put(ActionCommands.VALIDATE_URI, TestAgent::handleValidateUriCommand); actionHandlers.put(ActionCommands.VALIDATE_UUID, TestAgent::handleValidateUuidCommand); - actionHandlers.put(ActionCommands.SERIALIZE_UUID, TestAgent::handleLongSerializeUuidCommand); - actionHandlers.put(ActionCommands.DESERIALIZE_UUID, TestAgent::handleLongDeserializeUuidCommand); + actionHandlers.put(ActionCommands.SERIALIZE_UUID, TestAgent::handleSerializeUuidCommand); + actionHandlers.put(ActionCommands.DESERIALIZE_UUID, TestAgent::handleDeserializeUuidCommand); actionHandlers.put(ActionCommands.VALIDATE_UATTRIBUTES, TestAgent::handleUAttributesValidateCommand); - actionHandlers.put(ActionCommands.MICRO_SERIALIZE_URI, TestAgent::handleMicroSerializeUuriCommand); - actionHandlers.put(ActionCommands.MICRO_DESERIALIZE_URI, TestAgent::handleMicroDeserializeUuriCommand); + actionHandlers.put(ActionCommands.INITIALIZE_TRANSPORT, TestAgent::handleInitializeTransportCommand); + } + + static { + RESPONSE_URI = UUri.newBuilder().setUeId(1).setUeVersionMajor(1).setResourceId(0).build(); } static { try { - transport = new SocketUTransport(); + transport = new SocketUTransport(RESPONSE_URI); clientSocket = new Socket(Constant.TEST_MANAGER_IP, Constant.TEST_MANAGER_PORT); } catch (IOException e) { throw new RuntimeException(e); } } - public static void processMessage(Map jsonData) throws IOException { + public static void processMessage(Map jsonData) throws InterruptedException, ExecutionException { String action = (String) jsonData.get("action"); if (actionHandlers.containsKey(action)) { - UStatus status = (UStatus) actionHandlers.get(action).handle(jsonData); + CompletableFuture status = (CompletableFuture) actionHandlers.get(action).handle(jsonData); if (status != null) { String testID = (String) jsonData.get("test_id"); - sendToTestManager(status, action, testID); + sendToTestManager(status.get(), action, testID); } } } @@ -127,7 +139,7 @@ private static void sendToTestManager(Message proto, String action, String recei private static void writeDataToTMSocket(JSONObject responseDict, String action) { responseDict.put("action", action); - responseDict.put("ue", "java"); + responseDict.put("ue", sdkName); try { OutputStream outputStream = clientSocket.getOutputStream(); outputStream.write(responseDict.toString().getBytes(StandardCharsets.UTF_8)); @@ -139,7 +151,27 @@ private static void writeDataToTMSocket(JSONObject responseDict, String action) } } - private static UStatus handleSendCommand(Map jsonData) { + private static Object handleInitializeTransportCommand(Map jsonData) { + UUri uri = (UUri) ProtoConverter.dictToProto((Map) jsonData.get("data"), UUri.newBuilder()); + String testID = (String) jsonData.get("test_id"); + try { + if (transportName.equals("socket")) { + transport = new SocketUTransport(uri); + } else { + UStatus status = UStatus.newBuilder().setCode(UCode.FAILED_PRECONDITION).setMessage("").build(); + sendToTestManager(status, ActionCommands.INITIALIZE_TRANSPORT, testID); + } + } catch (IOException e) { + e.printStackTrace(); + UStatus status = UStatus.newBuilder().setCode(UCode.FAILED_PRECONDITION).setMessage("").build(); + sendToTestManager(status, ActionCommands.INITIALIZE_TRANSPORT, testID); + } + UStatus status = UStatus.newBuilder().setCode(UCode.OK).setMessage("").build(); + sendToTestManager(status, ActionCommands.INITIALIZE_TRANSPORT, testID); + return null; + } + + private static CompletionStage handleSendCommand(Map jsonData) { UMessage uMessage = (UMessage) ProtoConverter.dictToProto((Map) jsonData.get("data"), UMessage.newBuilder()); UAttributes uAttributesWithId = uMessage.getAttributes().toBuilder() @@ -148,41 +180,27 @@ private static UStatus handleSendCommand(Map jsonData) { return transport.send(uMessageWithId); } - private static UStatus handleRegisterListenerCommand(Map jsonData) { + private static CompletionStage handleRegisterListenerCommand(Map jsonData) { UUri uri = (UUri) ProtoConverter.dictToProto((Map) jsonData.get("data"), UUri.newBuilder()); return transport.registerListener(uri, listener); } - private static UStatus handleUnregisterListenerCommand(Map jsonData) { + private static CompletionStage handleUnregisterListenerCommand(Map jsonData) { UUri uri = (UUri) ProtoConverter.dictToProto((Map) jsonData.get("data"), UUri.newBuilder()); return transport.unregisterListener(uri, listener); } - private static Object handleInvokeMethodCommand(Map jsonData) { - Map data = (Map) jsonData.get("data"); - // Convert data and payload to protocol buffers - UUri uri = (UUri) ProtoConverter.dictToProto(data, UUri.newBuilder()); - UPayload payload = (UPayload) ProtoConverter.dictToProto((Map) data.get("payload"), - UPayload.newBuilder()); - CompletionStage responseFuture = transport.invokeMethod(uri, payload, - CallOptions.newBuilder().setTtl(10000).build()); - responseFuture.whenComplete((responseMessage, exception) -> { - sendToTestManager(responseMessage, ActionCommands.INVOKE_METHOD_COMMAND, (String) jsonData.get("test_id")); - }); - return null; - } - - private static Object handleLongSerializeUriCommand(Map jsonData) { + private static Object handleSerializeUriCommand(Map jsonData) { Map data = (Map) jsonData.get("data"); UUri uri = (UUri) ProtoConverter.dictToProto(data, UUri.newBuilder()); - String serializedUuri = LongUriSerializer.instance().serialize(uri); + String serializedUuri = UriSerializer.serialize(uri); String testID = (String) jsonData.get("test_id"); sendToTestManager(serializedUuri, ActionCommands.SERIALIZE_URI, testID); return null; } - private static Object handleLongDeserializeUriCommand(Map jsonData) { - UUri uri = LongUriSerializer.instance().deserialize(jsonData.get("data").toString()); + private static Object handleDeserializeUriCommand(Map jsonData) { + UUri uri = UriSerializer.deserialize(jsonData.get("data").toString()); String testID = (String) jsonData.get("test_id"); sendToTestManager(uri, ActionCommands.DESERIALIZE_URI, testID); return null; @@ -195,42 +213,37 @@ private static Object handleValidateUriCommand(Map jsonData) { UUri uri = (UUri) ProtoConverter.dictToProto(uriValue, UUri.newBuilder()); - Function validatorFunc = null; + Function validatorFunc = null; Function validatorFuncBool = null; switch (valType) { - case "uri": - validatorFunc = UriValidator::validate; - break; - case "rpc_response": - validatorFunc = UriValidator::validateRpcResponse; - break; - case "rpc_method": - validatorFunc = UriValidator::validateRpcMethod; - break; case "is_empty": - validatorFuncBool = UriValidator::isEmpty; + validatorFunc = UriValidator::isEmpty; break; - case "is_resolved": - validatorFuncBool = UriValidator::isResolved; + case "is_rpc_method": + validatorFunc = UriValidator::isRpcMethod; break; - case "is_micro_form": - validatorFuncBool = UriValidator::isMicroForm; + case "is_rpc_response": + validatorFunc = UriValidator::isRpcResponse; break; - case "is_long_form": - validatorFuncBool = UriValidator::isLongForm; + case "is_default_resource_id": + validatorFunc = UriValidator::isDefaultResourceId; + break; + case "is_topic": + validatorFunc = UriValidator::isTopic; break; } String testID = (String) jsonData.get("test_id"); if (validatorFunc != null) { - ValidationResult status = validatorFunc.apply(uri); - String result = status.isSuccess() ? "True" : "False"; - String message = status.getMessage(); - sendToTestManager(Map.of("result", result, "message", message), ActionCommands.VALIDATE_URI, testID); - } else if (validatorFuncBool != null) { - Boolean status = validatorFuncBool.apply(uri); - String result = status ? "True" : "False"; + Boolean status = validatorFunc.apply(uri); + String result = status.toString().equals("true") ? "True" : "False"; + sendToTestManager(Map.of("result", result, "message", ""), ActionCommands.VALIDATE_URI, testID); + } else if (valType.equals("matches")) { + String uriToMatch = (String) data.get("uuri_2"); + UUri convertedMatch = UriSerializer.deserialize(uriToMatch); + Boolean status = UriValidator.matches(uri, convertedMatch); + String result = status.toString().equals("true") ? "True" : "False"; sendToTestManager(Map.of("result", result, "message", ""), ActionCommands.VALIDATE_URI, testID); } @@ -246,7 +259,7 @@ public static Object handleUAttributesValidateCommand(Map jsonDa if (data.get("attributes") != null) { attributes = (UAttributes) ProtoConverter.dictToProto((Map) data.get("attributes"), UAttributes.newBuilder()); - if ("default".equals(attributes.getSink().getAuthority().getName())) { + if ("default".equals(attributes.getSink().getAuthorityName())) { attributes = attributes.toBuilder().setSink(UUri.getDefaultInstance()).build(); } } else { @@ -455,61 +468,32 @@ public static Object handleValidateUuidCommand(Map jsonData) { return null; } - private static Object handleLongSerializeUuidCommand(Map jsonData) { + private static Object handleSerializeUuidCommand(Map jsonData) { Map data = (Map) jsonData.get("data"); UUID uuid = (UUID) ProtoConverter.dictToProto(data, UUID.newBuilder()); - String serializedUUid = LongUuidSerializer.instance().serialize(uuid); + String serializedUUid = UuidSerializer.serialize(uuid); String testID = (String) jsonData.get("test_id"); sendToTestManager(serializedUUid, ActionCommands.SERIALIZE_UUID, testID); return null; } - private static Object handleLongDeserializeUuidCommand(Map jsonData) { - UUID uuid = LongUuidSerializer.instance().deserialize(jsonData.get("data").toString()); + private static Object handleDeserializeUuidCommand(Map jsonData) { + UUID uuid = UuidSerializer.deserialize(jsonData.get("data").toString()); String testID = (String) jsonData.get("test_id"); sendToTestManager(uuid, ActionCommands.DESERIALIZE_UUID, testID); return null; } - private static Object handleMicroSerializeUuriCommand(Map jsonData) { - Map data = (Map) jsonData.get("data"); - UUri uri = (UUri) ProtoConverter.dictToProto(data, UUri.newBuilder()); - byte[] serializedUuri = MicroUriSerializer.instance().serialize(uri); - String serializedUuriAsStr = ""; - try { - serializedUuriAsStr = new String(serializedUuri, "ISO-8859-1"); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - return null; - } - String testID = (String) jsonData.get("test_id"); - sendToTestManager(serializedUuriAsStr, ActionCommands.MICRO_SERIALIZE_URI, testID); - return null; - } - - private static Object handleMicroDeserializeUuriCommand(Map jsonData) { - String microSerializedUuriAsStr = (String) jsonData.get("data"); - byte[] microSerializedUuri = microSerializedUuriAsStr.getBytes(StandardCharsets.ISO_8859_1); - UUri uri = MicroUriSerializer.instance().deserialize(microSerializedUuri); - - String testID = (String) jsonData.get("test_id"); - sendToTestManager(uri, ActionCommands.MICRO_DESERIALIZE_URI, testID); - return null; - } - private static void handleOnReceive(UMessage uMessage) { logger.info("Java on_receive called: " + uMessage); if (uMessage.getAttributes().getType().equals(UMessageType.UMESSAGE_TYPE_REQUEST)) { UAttributes reqAttributes = uMessage.getAttributes(); - UAttributes uAttributes = UAttributesBuilder.response(reqAttributes.getSink(), reqAttributes.getSource(), - UPriority.UPRIORITY_CS4, reqAttributes.getId()).build(); + StringValue stringValue = StringValue.newBuilder().setValue("SuccessRPCResponse").build(); Any anyObj = Any.pack(stringValue); + UPayload uPayload = new UPayload(anyObj.toByteString(), UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY); + UMessage resMsg = UMessageBuilder.response(reqAttributes).build(uPayload); - UPayload uPayload = UPayload.newBuilder().setValue(anyObj.toByteString()) - .setFormat(UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY).build(); - - UMessage resMsg = UMessage.newBuilder().setAttributes(uAttributes).setPayload(uPayload).build(); transport.send(resMsg); } else { sendToTestManager(uMessage, ActionCommands.RESPONSE_ON_RECEIVE); @@ -517,15 +501,51 @@ private static void handleOnReceive(UMessage uMessage) { } - public static void main(String[] args) throws IOException { - Thread receiveThread = new Thread(TestAgent::receiveFromTM); + @SuppressWarnings("null") + public static void main(String[] args) { + + Options options = new Options(); + + Option input = new Option("t", "transport", true, "Select Transport"); + input.setRequired(true); + options.addOption(input); + + Option output = new Option("s", "sdkname", true, "Select SDK Name"); + output.setRequired(true); + options.addOption(output); + + CommandLineParser parser = new DefaultParser(); + HelpFormatter formatter = new HelpFormatter(); + CommandLine cmd = null;//not a good practice, it serves it purpose + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.out.println(e.getMessage()); + formatter.printHelp("utility-name", options); + + System.exit(1); + } + + transportName = cmd.getOptionValue("transport"); + sdkName = cmd.getOptionValue("sdkname"); + + Thread receiveThread = new Thread(() -> { + try { + receiveFromTM(); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + }); receiveThread.start(); JSONObject obj = new JSONObject(); - obj.put("SDK_name", "java"); + obj.put("SDK_name", sdkName); sendToTestManager(obj, "initialize"); } - public static void receiveFromTM() { + public static void receiveFromTM() throws InterruptedException, ExecutionException { try { while (true) { byte[] buffer = new byte[Constant.BYTES_MSG_LENGTH]; @@ -551,4 +571,4 @@ private interface ActionHandler { Object handle(Map jsonData); } -} +} \ No newline at end of file diff --git a/test_manager/testData/workflow_test_data.json b/test_manager/testData/workflow_test_data.json index d5c9a350..508abfe4 100644 --- a/test_manager/testData/workflow_test_data.json +++ b/test_manager/testData/workflow_test_data.json @@ -2,20 +2,20 @@ { "feature_name" : "uuid_validator", "path": "validators", - "ue1": ["python"], + "ue1": ["python", "java"], "transports": ["socket"] }, { "feature_name" : "register_and_send", "path": "transport_rpc", - "ue1": ["python"], - "ue2": ["python"], + "ue1": ["python", "java"], + "ue2": ["python", "java"], "transports": ["socket"] }, { "feature_name" : "register_and_unregister", "path": "transport_rpc", - "ue1": ["python"], + "ue1": ["python", "java"], "transports": ["socket"] } ] \ No newline at end of file diff --git a/up_client_socket/java/pom.xml b/up_client_socket/java/pom.xml index 7f1bc12a..35310104 100644 --- a/up_client_socket/java/pom.xml +++ b/up_client_socket/java/pom.xml @@ -9,18 +9,27 @@ 1.0-SNAPSHOT + 17 11 11 UTF-8 + 1.9.1 + 5.9.1 + 4.12 - - - - jitpack.io - https://jitpack.io - - - + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + 3.13.0 + + + com.google.protobuf @@ -33,15 +42,22 @@ 2.4.2 - com.github.eclipse-uprotocol + org.eclipse.uprotocol up-java - main-SNAPSHOT + 0.2.0 org.json json 20231013 + + + org.mockito + mockito-junit-jupiter + 3.12.4 + test + \ No newline at end of file diff --git a/up_client_socket/java/src/main/java/org/eclipse/uprotocol/SocketUTransport.java b/up_client_socket/java/src/main/java/org/eclipse/uprotocol/SocketUTransport.java index 9e902271..1a51efd8 100644 --- a/up_client_socket/java/src/main/java/org/eclipse/uprotocol/SocketUTransport.java +++ b/up_client_socket/java/src/main/java/org/eclipse/uprotocol/SocketUTransport.java @@ -1,274 +1,241 @@ /** - * SPDX-FileCopyrightText: Copyright (c) 2024 Contributors to the Eclipse Foundation - * + * SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation + *

* See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. - * - * 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. - * SPDX-FileType: SOURCE + *

+ * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + *

* SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.uprotocol; - -import org.eclipse.uprotocol.rpc.RpcClient; -import org.eclipse.uprotocol.transport.UListener; -import org.eclipse.uprotocol.transport.UTransport; -import org.eclipse.uprotocol.transport.builder.UAttributesBuilder; -import org.eclipse.uprotocol.uri.factory.UResourceBuilder; -import org.eclipse.uprotocol.uri.validator.UriValidator; -import org.eclipse.uprotocol.v1.*; -import org.eclipse.uprotocol.validation.ValidationResult; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.concurrent.*; -import java.util.logging.Level; -import java.util.logging.Logger; - -public class SocketUTransport implements UTransport, RpcClient { - private static final Logger logger = Logger.getLogger("JavaSocketUTransport"); - private static final String DISPATCHER_IP = "127.0.0.1"; - private static final Integer DISPATCHER_PORT = 44444; - private static final int BYTES_MSG_LENGTH = 32767; - private static final UUri RESPONSE_URI; - - static { - RESPONSE_URI = UUri.newBuilder().setEntity(UEntity.newBuilder().setName("test_agent_java").setVersionMajor(1)) - .setResource(UResourceBuilder.forRpcResponse()).build(); - } - - private final Socket socket; - private final ConcurrentHashMap> reqid_to_future; - private final ConcurrentHashMap> uri_to_listener; - private final Object lock = new Object(); - - - public SocketUTransport() throws IOException { - reqid_to_future = new ConcurrentHashMap<>(); - uri_to_listener = new ConcurrentHashMap<>(); - socket = new Socket(DISPATCHER_IP, DISPATCHER_PORT); - ExecutorService executor = Executors.newFixedThreadPool(5); - executor.submit(this::listen); - executor.shutdown(); - } - - /** - * Listens for incoming messages on the socket input stream from dispatcher. - * Messages are processed based on their type: PUBLISH, REQUEST, or RESPONSE. - * Handles each message accordingly by invoking corresponding handler methods. - */ - private void listen() { - try { - while (true) { - byte[] buffer = new byte[BYTES_MSG_LENGTH]; - InputStream inputStream = socket.getInputStream(); - int readSize = inputStream.read(buffer); - - if (readSize < 0) { - if (!socket.isClosed()) { - socket.close(); - } - return; - } - UMessage umsg = UMessage.parseFrom(Arrays.copyOfRange(buffer, 0, readSize)); - UAttributes attributes = umsg.getAttributes(); - String logMessage = " Received uMessage"; - - switch (attributes.getType()) { - case UMESSAGE_TYPE_PUBLISH: - handlePublishMessage(umsg); - break; - case UMESSAGE_TYPE_REQUEST: - handleRequestMessage(umsg); - break; - case UMESSAGE_TYPE_RESPONSE: - handleResponseMessage(umsg); - break; - default: - logger.warning(logMessage + " with unknown message type."); - } - - logger.info(logMessage); - } - } catch (IOException e) { - try { - if (!socket.isClosed()) { - socket.close(); - } - } catch (IOException ioException) { - logger.log(Level.SEVERE, "Error while closing socket: " + ioException.getMessage(), ioException); - } - logger.log(Level.SEVERE, "Error while listening for messages: " + e.getMessage(), e); - } - } - - /** - * Handles a publish message by notifying listeners registered for the source URI. - * - * @param umsg The publish message to handle. - */ - private void handlePublishMessage(UMessage umsg) { - UUri uri = umsg.getAttributes().getSource(); - notifyListeners(uri, umsg); - } - - /** - * Handles a request message by notifying listeners registered for the target URI. - * - * @param umsg The request message to handle. - */ - private void handleRequestMessage(UMessage umsg) { - UUri uri = umsg.getAttributes().getSink(); - notifyListeners(uri, umsg); - } - - /** - * Notifies all listeners registered for the given URI with the provided message. - * - * @param uri The URI for which listeners are to be notified. - * @param umsg The message to be delivered to the listeners. - */ - private void notifyListeners(UUri uri, UMessage umsg) { - - synchronized (lock) { - - ArrayList listeners = uri_to_listener.get(uri); - if (listeners != null) { - logger.info("Handle Uri"); - listeners.forEach(listener -> listener.onReceive(umsg)); - } else { - logger.info(getClass().getSimpleName() + " Uri not found in Listener Map, discarding..."); - } - } - } - - /** - * Handles the response message received from the server. - * Completes the CompletableFuture associated with the request ID - * if it exists in the map of pending request futures. - * - * @param umsg The response message to handle. - */ - private void handleResponseMessage(UMessage umsg) { - UUID requestId = umsg.getAttributes().getReqid(); - CompletionStage responseFuture = reqid_to_future.remove(requestId); - if (responseFuture != null) { - responseFuture.toCompletableFuture().complete(umsg); - } - } - - /** - * Sends the provided message over the socket connection. - * - * @param message The message to be sent. - * @return A status indicating the outcome of the send operation. - */ - public UStatus send(UMessage message) { - byte[] umsgSerialized = message.toByteArray(); - try { - OutputStream outputStream = socket.getOutputStream(); - outputStream.write(umsgSerialized); - logger.info("uMessage Sent to dispatcher fron java socket transport"); - return UStatus.newBuilder().setCode(UCode.OK).setMessage("OK").build(); - } catch (IOException e) { - logger.log(Level.SEVERE, "INTERNAL ERROR: ", e); - return UStatus.newBuilder().setCode(UCode.INTERNAL).setMessage("INTERNAL ERROR: " + e.getMessage()).build(); - } - } - - /** - * Registers the specified listener for the given topic URI. - * - * @param topic The URI of the topic to register the listener for. - * @param listener The listener to be registered. - * @return A status indicating the outcome of the registration operation. - */ - public UStatus registerListener(UUri topic, UListener listener) { - ValidationResult result = UriValidator.validate(topic); - if (result.isFailure()) { - return result.toStatus(); - } - uri_to_listener.computeIfAbsent(topic, k -> new ArrayList<>()).add(listener); - return UStatus.newBuilder().setCode(UCode.OK).setMessage("OK").build(); - } - - /** - * Unregisters the specified listener from the given topic URI. - * - * @param topic The URI of the topic to unregister the listener from. - * @param listener The listener to be removed. - * @return A status indicating the outcome of the unregistration operation. - */ - public UStatus unregisterListener(UUri topic, UListener listener) { - ValidationResult result = UriValidator.validate(topic); - if (result.isFailure()) { - return result.toStatus(); - } - ArrayList listeners = uri_to_listener.get(topic); - if (listeners != null && listeners.remove(listener)) { - if (listeners.isEmpty()) { - uri_to_listener.remove(topic); - } - return UStatus.newBuilder().setCode(UCode.OK).setMessage("OK").build(); - } - return UStatus.newBuilder().setCode(UCode.NOT_FOUND).setMessage("Listener not found for the given UUri") - .build(); - } - - /** - * Invokes a remote method with provided parameters and returns a CompletableFuture for the response. - * - * @param methodUri The URI identifying the remote method to be invoked. - * @param requestPayload The payload of the request message. - * @param options The call options specifying timeout. - * @return A CompletableFuture that will hold the response message for the request. - */ - public CompletionStage invokeMethod(UUri methodUri, UPayload requestPayload, CallOptions options) { - UAttributes attributes = UAttributesBuilder.request(RESPONSE_URI, methodUri, UPriority.UPRIORITY_CS4, - options.getTtl()).build(); - UUID requestId = attributes.getId(); - CompletableFuture responseFuture = new CompletableFuture<>(); - reqid_to_future.put(requestId, responseFuture); - - Thread timeoutThread = new Thread(() -> timeoutCounter(responseFuture, requestId, options.getTtl())); - timeoutThread.start(); - - UMessage umsg = UMessage.newBuilder().setPayload(requestPayload).setAttributes(attributes).build(); - send(umsg); - return responseFuture; - } - - /** - * Waits for the specified timeout and completes the CompletableFuture exceptionally if no response is received. - * - * @param responseFuture The CompletableFuture to complete exceptionally. - * @param requestId The request ID associated with the response. - * @param timeout The timeout duration. - */ - private void timeoutCounter(CompletableFuture responseFuture, UUID requestId, int timeout) { - try { - Thread.sleep(timeout); - if (!responseFuture.isDone()) { - responseFuture.completeExceptionally(new TimeoutException( - "Not received response for request " + requestId.toString() + " within " + timeout + " ms")); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } -} + package org.eclipse.uprotocol; + + import org.eclipse.uprotocol.transport.UListener; + import org.eclipse.uprotocol.transport.UTransport; + import org.eclipse.uprotocol.uri.validator.UriFilter; + import org.eclipse.uprotocol.v1.UCode; + import org.eclipse.uprotocol.v1.UMessage; + import org.eclipse.uprotocol.v1.UStatus; + import org.eclipse.uprotocol.v1.UUri; + + import java.io.IOException; + import java.io.InputStream; + import java.io.OutputStream; + import java.net.Socket; + import java.util.AbstractMap; + import java.util.Arrays; + import java.util.HashMap; + import java.util.Map; + import java.util.concurrent.CompletableFuture; + import java.util.concurrent.CompletionStage; + import java.util.concurrent.ExecutorService; + import java.util.concurrent.Executors; + import java.util.logging.Level; + import java.util.logging.Logger; + + /** + * {@code SocketUTransport} is an implementation of the {@link UTransport} interface that communicates + * over a TCP socket. It listens for incoming messages from a dispatcher, processes them, and notifies + * registered listeners based on URI filters. It also supports sending messages and managing listeners. + *

+ * This implementation uses a fixed thread pool to handle incoming messages asynchronously and ensures + * that socket resources are properly managed and released. + *

+ */ + public class SocketUTransport implements UTransport { + private static final Logger logger = Logger.getLogger("JavaSocketUTransport"); + private static final String DISPATCHER_IP = "127.0.0.1"; + private static final Integer DISPATCHER_PORT = 44444; + private static final int BYTES_MSG_LENGTH = 32767; + private static final String INTERNAL_ERROR = "INTERNAL ERROR : "; + private final Socket socket; + private final Object lock = new Object(); + private final UUri source; + private Map, UListener> uriToListener = new HashMap<>(); + + /** + * Constructs a {@code SocketUTransport} instance and establishes a connection to the dispatcher. + * Initializes the socket and starts listening for incoming messages asynchronously. + * + * @param newSource The source URI to be used by this transport. + * @throws IOException If an I/O error occurs when creating the socket. + */ + public SocketUTransport(UUri newSource) throws IOException { + source = newSource; + uriToListener = new HashMap<>(); + socket = new Socket(DISPATCHER_IP, DISPATCHER_PORT); + ExecutorService executor = Executors.newFixedThreadPool(5); + executor.submit(this::listen); + executor.shutdown(); + } + + + /** + * Listens for incoming UMessages from the Dispatcher. + * Processes the incoming data if the listener is registered with a UMessage source and sink UURI filter. + */ + private void listen() { + try { + while (true) { + byte[] buffer = new byte[BYTES_MSG_LENGTH]; + InputStream inputStream = socket.getInputStream(); + int readSize = inputStream.read(buffer); + + if (readSize < 0) { + if (!socket.isClosed()) { + socket.close(); + } + return; + } + UMessage umsg = UMessage.parseFrom(Arrays.copyOfRange(buffer, 0, readSize)); + String logMessage = " Received uMessage"; + logger.info(logMessage); + notifyListeners(umsg); + } + } catch (IOException e) { + try { + if (!socket.isClosed()) { + socket.close(); + } + } catch (IOException ioException) { + logger.log(Level.SEVERE, String.format("Error while closing socket: %s", ioException.getMessage()), + ioException); + } + logger.log(Level.SEVERE, String.format("Error while listening for messages: %s", e.getMessage()), e); + } + } + + /** + * Notifies listeners registered for the source and sink URI filters about the incoming message. + * The message is matched against the registered URI filters, and the appropriate listeners are + * invoked asynchronously. + * + * @param umsg The message to be processed and dispatched to listeners. + */ + public void notifyListeners(UMessage umsg) { + synchronized (lock) { + boolean isMatch = false; + for (Map.Entry, UListener> entry : uriToListener.entrySet()) { + AbstractMap.SimpleEntry key = entry.getKey(); + UListener listener = entry.getValue(); + UriFilter uriFilter = new UriFilter(key.getKey(), key.getValue()); + boolean match = uriFilter.matches(umsg.getAttributes()); + if (match && listener != null) { + logger.info("Handle Uri"); + listener.onReceive(umsg); + isMatch = true; + } + } + if (!isMatch) { + logger.info("Uri not found in Listener Map, discarding..."); + } + } + } + + /** + * Registers a listener for the specified source and sink URI filters. + * + * @param sourceFilter The URI filter for the source. + * @param sinkFilter The URI filter for the sink. + * @param listener The listener to be registered. + */ + public void addListener(UUri sourceFilter, UUri sinkFilter, UListener listener) { + logger.info("listeners: " + sourceFilter + ", " + sinkFilter + ", " + listener); + AbstractMap.SimpleEntry key = new AbstractMap.SimpleEntry<>(sourceFilter, sinkFilter); + uriToListener.put(key, listener); + } + + /** + * Removes the listener registered for the specified source and sink URI filters. + * + * @param sourceFilter The URI filter for the source. + * @param sinkFilter The URI filter for the sink. + * @return A status indicating the outcome of the removal operation. + */ + public UStatus removeListener(UUri sourceFilter, UUri sinkFilter) { + AbstractMap.SimpleEntry key = new AbstractMap.SimpleEntry<>(sourceFilter, sinkFilter); + UListener listener = uriToListener.remove(key); + if (listener != null) { + return UStatus.newBuilder().setCode(UCode.OK).setMessage("Listener removed successfully").build(); + + } else { + return UStatus.newBuilder().setCode(UCode.NOT_FOUND).setMessage("Listener not found for the given URI") + .build(); + } + } + + /** + * Sends the provided message over the socket connection. + * + * @param message The message to be sent. + * @return A status indicating the outcome of the send operation. + */ + public CompletionStage send(UMessage message) { + byte[] umsgSerialized = message.toByteArray(); + try { + OutputStream outputStream = socket.getOutputStream(); + outputStream.write(umsgSerialized); + logger.info("uMessage Sent to dispatcher fron java socket transport"); + return CompletableFuture.completedFuture( + UStatus.newBuilder().setCode(UCode.OK).setMessage("uMessage Sent to dispatcher").build()); + } catch (IOException e) { + logger.log(Level.SEVERE, INTERNAL_ERROR, e); + return CompletableFuture.completedFuture( + UStatus.newBuilder().setCode(UCode.INTERNAL).setMessage(INTERNAL_ERROR + e.getMessage()).build()); + } + } + + /** + * Registers the specified listener for the given source and sink URI filters. + * + * @param sourceFilter The URI filter for the source. + * @param sinkFilter The URI filter for the sink. + * @param listener The listener to be registered. + * @return A status indicating the outcome of the register listener operation. + */ + public CompletionStage registerListener(UUri sourceFilter, UUri sinkFilter, UListener listener) { + addListener(sourceFilter, sinkFilter, listener); + return CompletableFuture.completedFuture(UStatus.newBuilder().setCode(UCode.OK).setMessage("OK").build()); + } + + + /** + * Unregisters the specified listener from the given source and sink URI filters. + * + * @param sourceFilter The URI filter for the source. + * @param sinkFilter The URI filter for the sink. + * @param listener The listener to be removed. + * @return A status indicating the outcome of the unregister listener operation. + */ + public CompletionStage unregisterListener(UUri sourceFilter, UUri sinkFilter, UListener listener) { + UStatus status = removeListener(sourceFilter, sinkFilter); + return CompletableFuture.completedFuture(status); + } + + + /** + * Closes the socket connection and releases any resources associated with it. + */ + @Override + public void close() { + try { + if (!socket.isClosed()) { + socket.close(); + } + } catch (IOException e) { + logger.log(Level.SEVERE, "INTERNAL ERROR: ", e); + } + } + + /** + * Returns the source URI of this transport. + * + * @return The source URI. + */ + public UUri getSource() { + return source; + } + } \ No newline at end of file