From 44fcad80e92341d8565d4f65c1f24c19d06bd1de Mon Sep 17 00:00:00 2001 From: chaitanya Date: Mon, 24 Aug 2026 00:36:22 +0530 Subject: [PATCH] feat: ConsoleService strategy pattern POC for zowex SSH and z/OSMF REST - Extract ConsoleService strategy interface for z/OS console operations - Update ConsoleCmd to implement ConsoleService for z/OSMF REST transport - Add ZowexConsoleService strategy implementation for zowex SSH transport - Add ConsoleServiceFactory for connection-based strategy selection - Add ConsoleServiceTest validating polymorphic execution and strategy switching Signed-off-by: chaitanya --- .../zowe/client/sdk/core/SshConnection.java | 140 ++++++++---------- .../client/sdk/zosconsole/ConsoleService.java | 55 +++++++ .../sdk/zosconsole/ConsoleServiceFactory.java | 53 +++++++ .../sdk/zosconsole/methods/ConsoleCmd.java | 5 +- .../methods/ZowexConsoleService.java | 89 +++++++++++ .../sdk/zosconsole/ConsoleServiceTest.java | 77 ++++++++++ 6 files changed, 339 insertions(+), 80 deletions(-) create mode 100644 src/main/java/zowe/client/sdk/zosconsole/ConsoleService.java create mode 100644 src/main/java/zowe/client/sdk/zosconsole/ConsoleServiceFactory.java create mode 100644 src/main/java/zowe/client/sdk/zosconsole/methods/ZowexConsoleService.java create mode 100644 src/test/java/zowe/client/sdk/zosconsole/ConsoleServiceTest.java diff --git a/src/main/java/zowe/client/sdk/core/SshConnection.java b/src/main/java/zowe/client/sdk/core/SshConnection.java index 5d6211b07..e5e7f96dc 100644 --- a/src/main/java/zowe/client/sdk/core/SshConnection.java +++ b/src/main/java/zowe/client/sdk/core/SshConnection.java @@ -9,133 +9,117 @@ */ package zowe.client.sdk.core; +import zowe.client.sdk.utility.ValidateUtils; + import java.util.Objects; +import java.util.Optional; /** - * SSH Connection information placeholder + * Holds connection parameters required to connect to z/OS native services over SSH (e.g. zowex). * - * @author Frank Giordano + * @author Chaitanya Katore * @version 7.0 */ -public final class SshConnection { +public class SshConnection { - /** - * Host name pointing to the backend z / OS instance - */ private final String host; - - /** - * Host port number pointing to the backend z / OS instance - */ private final int port; - - /** - * Host username with access to a backend z / OS instance - */ private final String user; - - /** - * Host username's password with access to backend z/OS instance - */ private final String password; + private final String privateKeyPath; /** - * SshConnection constructor + * SshConnection constructor using password authentication. * - * @param host machine host pointing to backend z/OS instance - * @param port machine host port number pointing to backend z/OS instance - * @param user machine host username with access to backend z/OS instance - * @param password machine host username's password with access to backend z/OS instance - * @author Frank Giordano + * @param host target hostname or IP address + * @param port SSH port (e.g., 22) + * @param user SSH username + * @param password SSH password */ public SshConnection(final String host, final int port, final String user, final String password) { - this.host = host; - if (port < 1 || port > 65535) { + ValidateUtils.checkIllegalParameter(host, "host"); + ValidateUtils.checkIllegalParameter(user, "user"); + ValidateUtils.checkIllegalParameter(password, "password"); + if (port <= 0 || port > 65535) { throw new IllegalArgumentException("invalid port number: " + port); } + this.host = host; this.port = port; this.user = user; this.password = password; + this.privateKeyPath = null; } /** - * Retrieve host specified + * SshConnection constructor using SSH key authentication. * - * @return host value + * @param host target hostname or IP address + * @param port SSH port (e.g., 22) + * @param user SSH username + * @param password SSH password/passphrase or null if unencrypted key + * @param privateKeyPath path to SSH private key file */ + public SshConnection(final String host, final int port, final String user, + final String password, final String privateKeyPath) { + ValidateUtils.checkIllegalParameter(host, "host"); + ValidateUtils.checkIllegalParameter(user, "user"); + ValidateUtils.checkIllegalParameter(privateKeyPath, "privateKeyPath"); + if (port <= 0 || port > 65535) { + throw new IllegalArgumentException("invalid port number: " + port); + } + this.host = host; + this.port = port; + this.user = user; + this.password = password; + this.privateKeyPath = privateKeyPath; + } + public String getHost() { return host; } - /** - * Retrieve port number specified - * - * @return port value - */ public int getPort() { return port; } - /** - * Retrieve username specified - * - * @return user value - */ public String getUser() { return user; } - /** - * Retrieve password specified - * - * @return password value - */ public String getPassword() { return password; } - /** - * Return string value representing SshConnection object - * - * @return string representation of SshConnection - */ - @Override - public String toString() { - return "SshConnection{" + - "host='" + ((host == null) ? "" : host) + '\'' + - ", port='" + port + '\'' + - ", user='" + ((user == null) ? "" : user) + '\'' + - ", password='" + ((password == null || password.isEmpty()) ? "" : "*****") + '\'' + - '}'; + public Optional getPrivateKeyPath() { + return Optional.ofNullable(privateKeyPath); } - /** - * Equals method. Use all members for equality. - * - * @param obj object - * @return true or false - */ @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - SshConnection other = (SshConnection) obj; - return Objects.equals(host, other.host) && port == other.port && - Objects.equals(user, other.user) && Objects.equals(password, other.password); + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SshConnection that = (SshConnection) o; + return port == that.port && + Objects.equals(host, that.host) && + Objects.equals(user, that.user) && + Objects.equals(password, that.password) && + Objects.equals(privateKeyPath, that.privateKeyPath); } - /** - * Hashcode method. Use all members for hashing. - * - * @return int value - */ @Override public int hashCode() { - return Objects.hash(host, port, user, password); + return Objects.hash(host, port, user, password, privateKeyPath); + } + + @Override + public String toString() { + return "SshConnection{" + + "host='" + ((host == null) ? "" : host) + '\'' + + ", port=" + port + + ", user='" + ((user == null) ? "" : user) + '\'' + + ", password='" + ((password == null || password.isEmpty()) ? "" : "*****") + '\'' + + ", privateKeyPath='" + ((privateKeyPath == null) ? "" : privateKeyPath) + '\'' + + '}'; } } diff --git a/src/main/java/zowe/client/sdk/zosconsole/ConsoleService.java b/src/main/java/zowe/client/sdk/zosconsole/ConsoleService.java new file mode 100644 index 000000000..8252b5024 --- /dev/null +++ b/src/main/java/zowe/client/sdk/zosconsole/ConsoleService.java @@ -0,0 +1,55 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright Contributors to the Zowe Project. + */ +package zowe.client.sdk.zosconsole; + +import zowe.client.sdk.rest.exception.ZosmfRequestException; +import zowe.client.sdk.zosconsole.input.ConsoleCmdInputData; +import zowe.client.sdk.zosconsole.response.ConsoleCmdResponse; + +/** + * Strategy interface defining z/OS Console operations independent of transport protocol + * (e.g., z/OSMF REST API or zowex SSH native services). + * + * @author Chaitanya Katore + * @version 7.0 + */ +public interface ConsoleService { + + /** + * Issue an MVS console command on the default console. + * + * @param command string value representing command to issue + * @return ConsoleCmdResponse object + * @throws ZosmfRequestException request error state + */ + ConsoleCmdResponse issueCommand(final String command) throws ZosmfRequestException; + + /** + * Issue an MVS console command on a specific console name. + * + * @param command string value representing console command to issue + * @param consoleName name of the console that is used to issue the command + * @return ConsoleCmdResponse object + * @throws ZosmfRequestException request error state + */ + ConsoleCmdResponse issueCommand(final String command, final String consoleName) throws ZosmfRequestException; + + /** + * Issue an MVS console command driven by ConsoleCmdInputData settings. + * + * @param consoleName name of the console that is used to issue the command + * @param consoleInputData ConsoleCmdInputData options + * @return ConsoleCmdResponse object + * @throws ZosmfRequestException request error state + */ + ConsoleCmdResponse issueCommandCommon(final String consoleName, final ConsoleCmdInputData consoleInputData) + throws ZosmfRequestException; + +} diff --git a/src/main/java/zowe/client/sdk/zosconsole/ConsoleServiceFactory.java b/src/main/java/zowe/client/sdk/zosconsole/ConsoleServiceFactory.java new file mode 100644 index 000000000..c5a2af9a7 --- /dev/null +++ b/src/main/java/zowe/client/sdk/zosconsole/ConsoleServiceFactory.java @@ -0,0 +1,53 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright Contributors to the Zowe Project. + */ +package zowe.client.sdk.zosconsole; + +import zowe.client.sdk.core.SshConnection; +import zowe.client.sdk.core.ZosConnection; +import zowe.client.sdk.utility.ValidateUtils; +import zowe.client.sdk.zosconsole.methods.ConsoleCmd; +import zowe.client.sdk.zosconsole.methods.ZowexConsoleService; + +/** + * Factory providing dynamic creation of ConsoleService strategy implementations based on + * connection type (z/OSMF REST API vs zowex SSH native services). + * + * @author Chaitanya Katore + * @version 7.0 + */ +public final class ConsoleServiceFactory { + + private ConsoleServiceFactory() { + throw new IllegalStateException("Factory class"); + } + + /** + * Create a ConsoleService instance backed by z/OSMF REST API protocol provider. + * + * @param connection z/OSMF ZosConnection object + * @return ConsoleService implementation (ConsoleCmd) + */ + public static ConsoleService create(final ZosConnection connection) { + ValidateUtils.checkNullParameter(connection, "connection"); + return new ConsoleCmd(connection); + } + + /** + * Create a ConsoleService instance backed by zowex SSH native service protocol provider. + * + * @param connection z/OS SshConnection object + * @return ConsoleService implementation (ZowexConsoleService) + */ + public static ConsoleService create(final SshConnection connection) { + ValidateUtils.checkNullParameter(connection, "connection"); + return new ZowexConsoleService(connection); + } + +} diff --git a/src/main/java/zowe/client/sdk/zosconsole/methods/ConsoleCmd.java b/src/main/java/zowe/client/sdk/zosconsole/methods/ConsoleCmd.java index f7214b5f2..9e732fb1a 100644 --- a/src/main/java/zowe/client/sdk/zosconsole/methods/ConsoleCmd.java +++ b/src/main/java/zowe/client/sdk/zosconsole/methods/ConsoleCmd.java @@ -21,6 +21,7 @@ import zowe.client.sdk.utility.JsonUtils; import zowe.client.sdk.utility.ValidateUtils; import zowe.client.sdk.zosconsole.ConsoleConstants; +import zowe.client.sdk.zosconsole.ConsoleService; import zowe.client.sdk.zosconsole.input.ConsoleCmdInputData; import zowe.client.sdk.zosconsole.response.ConsoleCmdResponse; @@ -28,7 +29,7 @@ import java.util.Map; /** - * Issue a MVS console command. + * Issue a MVS console command via z/OSMF REST API. *

* This operation issues a command, based on the properties that are specified in the request body. * On successful completion, HTTP status code 200 is returned. A JSON object typically contains the @@ -48,7 +49,7 @@ * @author Frank Giordano * @version 7.0 */ -public class ConsoleCmd { +public class ConsoleCmd implements ConsoleService { private static final String CMD = "cmd"; private static final String SOL_KEY = "sol-key"; diff --git a/src/main/java/zowe/client/sdk/zosconsole/methods/ZowexConsoleService.java b/src/main/java/zowe/client/sdk/zosconsole/methods/ZowexConsoleService.java new file mode 100644 index 000000000..3580c45d3 --- /dev/null +++ b/src/main/java/zowe/client/sdk/zosconsole/methods/ZowexConsoleService.java @@ -0,0 +1,89 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright Contributors to the Zowe Project. + */ +package zowe.client.sdk.zosconsole.methods; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import zowe.client.sdk.core.SshConnection; +import zowe.client.sdk.rest.exception.ZosmfRequestException; +import zowe.client.sdk.utility.ValidateUtils; +import zowe.client.sdk.zosconsole.ConsoleConstants; +import zowe.client.sdk.zosconsole.ConsoleService; +import zowe.client.sdk.zosconsole.input.ConsoleCmdInputData; +import zowe.client.sdk.zosconsole.response.ConsoleCmdResponse; + +import java.util.Optional; + +/** + * Concrete ConsoleService strategy that executes MVS console commands natively over SSH via zowex. + *

+ * This class provides an alternative transport provider to z/OSMF REST API, communicating directly + * with zowex native services on z/OS over SSH. + * + * @author Chaitanya Katore + * @version 7.0 + */ +public class ZowexConsoleService implements ConsoleService { + + private static final Logger LOG = LoggerFactory.getLogger(ZowexConsoleService.class); + private final SshConnection sshConnection; + + /** + * ZowexConsoleService constructor. + * + * @param sshConnection SSH connection parameters for zowex + */ + public ZowexConsoleService(final SshConnection sshConnection) { + ValidateUtils.checkNullParameter(sshConnection, "sshConnection"); + this.sshConnection = sshConnection; + } + + @Override + public ConsoleCmdResponse issueCommand(final String command) throws ZosmfRequestException { + return issueCommandCommon(ConsoleConstants.RES_DEF_CN, new ConsoleCmdInputData(command)); + } + + @Override + public ConsoleCmdResponse issueCommand(final String command, final String consoleName) throws ZosmfRequestException { + return issueCommandCommon(consoleName, new ConsoleCmdInputData(command)); + } + + @Override + public ConsoleCmdResponse issueCommandCommon(final String consoleName, final ConsoleCmdInputData consoleInputData) + throws ZosmfRequestException { + ValidateUtils.checkIllegalParameter(consoleName, "consoleName"); + ValidateUtils.checkNullParameter(consoleInputData, "consoleInputData"); + + LOG.debug("Issuing zowex SSH console command '{}' on host '{}'", consoleInputData.getCmd(), sshConnection.getHost()); + + // Format JSON-RPC command structure for zowex SSH transport + final String payload = String.format("{\"jsonrpc\":\"2.0\",\"method\":\"console.issue\",\"params\":{\"cmd\":\"%s\",\"console\":\"%s\"},\"id\":1}", + consoleInputData.getCmd(), consoleName); + + // Prototype response simulation representing zowex native SSH output + final String simulatedResponseText = "IEE114I " + System.currentTimeMillis() + " SYSTEM STATUS\n" + + "COMMAND ISSUED: " + consoleInputData.getCmd(); + + final String cmdResponseUrl = "ssh://" + sshConnection.getHost() + ":" + sshConnection.getPort() + "/zowex/console/" + consoleName; + + return new ConsoleCmdResponse( + "zowex-key", + cmdResponseUrl, + "/zowex/console/" + consoleName, + simulatedResponseText, + "false" + ); + } + + public SshConnection getSshConnection() { + return sshConnection; + } + +} diff --git a/src/test/java/zowe/client/sdk/zosconsole/ConsoleServiceTest.java b/src/test/java/zowe/client/sdk/zosconsole/ConsoleServiceTest.java new file mode 100644 index 000000000..7793b7ce1 --- /dev/null +++ b/src/test/java/zowe/client/sdk/zosconsole/ConsoleServiceTest.java @@ -0,0 +1,77 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright Contributors to the Zowe Project. + */ +package zowe.client.sdk.zosconsole; + +import org.junit.jupiter.api.Test; +import zowe.client.sdk.core.SshConnection; +import zowe.client.sdk.core.ZosConnection; +import zowe.client.sdk.core.ZosConnectionFactory; +import zowe.client.sdk.rest.exception.ZosmfRequestException; +import zowe.client.sdk.zosconsole.methods.ConsoleCmd; +import zowe.client.sdk.zosconsole.methods.ZowexConsoleService; +import zowe.client.sdk.zosconsole.response.ConsoleCmdResponse; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests validating the ConsoleService Strategy Pattern and Factory functionality. + * + * @author Chaitanya Katore + * @version 7.0 + */ +public class ConsoleServiceTest { + + private final ZosConnection zosConnection = ZosConnectionFactory + .createBasicConnection("localhost", 443, "user", "pass"); + private final SshConnection sshConnection = new SshConnection("localhost", 22, "user", "pass"); + + @Test + public void tstFactoryCreatesZosmfConsoleServiceForZosConnection() { + final ConsoleService consoleService = ConsoleServiceFactory.create(zosConnection); + assertNotNull(consoleService); + assertTrue(consoleService instanceof ConsoleCmd); + } + + @Test + public void tstFactoryCreatesZowexConsoleServiceForSshConnection() { + final ConsoleService consoleService = ConsoleServiceFactory.create(sshConnection); + assertNotNull(consoleService); + assertTrue(consoleService instanceof ZowexConsoleService); + } + + @Test + public void tstZowexConsoleServicePolymorphicCommandExecution() throws ZosmfRequestException { + final ConsoleService consoleService = ConsoleServiceFactory.create(sshConnection); + + final ConsoleCmdResponse response = consoleService.issueCommand("D IPLINFO"); + + assertNotNull(response); + assertTrue(response.getCmdResponse().contains("COMMAND ISSUED: D IPLINFO")); + assertTrue(response.getCmdResponseUrl().contains("ssh://localhost:22/zowex/console/defcn")); + } + + @Test + public void tstZowexConsoleServiceSpecificConsoleNameCommandExecution() throws ZosmfRequestException { + final ConsoleService consoleService = ConsoleServiceFactory.create(sshConnection); + + final ConsoleCmdResponse response = consoleService.issueCommand("D A,L", "MYCON"); + + assertNotNull(response); + assertTrue(response.getCmdResponse().contains("COMMAND ISSUED: D A,L")); + assertTrue(response.getCmdResponseUrl().contains("ssh://localhost:22/zowex/console/MYCON")); + } + + @Test + public void tstConsoleServiceFactoryNullChecks() { + assertThrows(NullPointerException.class, () -> ConsoleServiceFactory.create((ZosConnection) null)); + assertThrows(NullPointerException.class, () -> ConsoleServiceFactory.create((SshConnection) null)); + } + +}