From de19b0356428dd48c21b829ac2190b9dcc2b6fd8 Mon Sep 17 00:00:00 2001 From: Rob Stryker Date: Tue, 1 Sep 2026 17:00:38 -0400 Subject: [PATCH] Add client-side import/export ability to client jar only Signed-off-by: Rob Stryker Missed file Signed-off-by: Rob Stryker --- .../rsp/client/cli/ServerDescriptorUtil.java | 154 ++++++++++++++++++ .../client/cli/StandardCommandHandler.java | 143 ++++++++++++++++ 2 files changed, 297 insertions(+) create mode 100644 client/jars/org.jboss.tools.rsp.client.cli/src/main/java/org/jboss/tools/rsp/client/cli/ServerDescriptorUtil.java diff --git a/client/jars/org.jboss.tools.rsp.client.cli/src/main/java/org/jboss/tools/rsp/client/cli/ServerDescriptorUtil.java b/client/jars/org.jboss.tools.rsp.client.cli/src/main/java/org/jboss/tools/rsp/client/cli/ServerDescriptorUtil.java new file mode 100644 index 000000000..3c80640a4 --- /dev/null +++ b/client/jars/org.jboss.tools.rsp.client.cli/src/main/java/org/jboss/tools/rsp/client/cli/ServerDescriptorUtil.java @@ -0,0 +1,154 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, Inc. Distributed under license by Red Hat, Inc. + * All rights reserved. This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is + * available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: Red Hat, Inc. + ******************************************************************************/ +package org.jboss.tools.rsp.client.cli; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ServerDescriptorUtil { + + public static final String VARIABLE_PREFIX = "${rsp_import_export/"; + public static final String VARIABLE_SUFFIX = "}"; + public static final String FORMAT_VERSION_KEY = "rsp.descriptor.format.version"; + public static final String FORMAT_VERSION = "1.0"; + public static final String TYPE_ID_KEY = "org.jboss.tools.rsp.server.typeId"; + private static final Set EXCLUDED_EXPORT_KEYS = new HashSet<>( + Arrays.asList("id", "deployables")); + + private static final Pattern VARIABLE_PATTERN = Pattern.compile( + "\\$\\{rsp_import_export/([^}]+)\\}"); + + private ServerDescriptorUtil() { + } + + public static boolean isFilesystemPath(String value) { + if (value == null || value.isEmpty()) { + return false; + } + String trimmed = value.trim(); + + if (trimmed.startsWith("file://")) { + return true; + } + + if (trimmed.startsWith("/")) { + String[] segments = trimmed.split("/"); + long nonEmpty = Arrays.stream(segments).filter(s -> !s.isEmpty()).count(); + return nonEmpty >= 2; + } + + if (trimmed.length() >= 3 + && Character.isLetter(trimmed.charAt(0)) + && trimmed.charAt(1) == ':' + && (trimmed.charAt(2) == '\\' || trimmed.charAt(2) == '/')) { + return true; + } + + return false; + } + + public static Map detectPathAttributes(Map attrs) { + Map pathAttrs = new LinkedHashMap<>(); + for (Map.Entry entry : attrs.entrySet()) { + String key = entry.getKey(); + if (EXCLUDED_EXPORT_KEYS.contains(key) + || FORMAT_VERSION_KEY.equals(key) + || TYPE_ID_KEY.equals(key)) { + continue; + } + Object val = entry.getValue(); + if (val instanceof String && isFilesystemPath((String) val)) { + pathAttrs.put(key, (String) val); + } + } + return pathAttrs; + } + + public static Map substitutePathsForExport(Map attrs) { + Map pathAttrs = detectPathAttributes(attrs); + + List> sortedPaths = new ArrayList<>(pathAttrs.entrySet()); + sortedPaths.sort((a, b) -> b.getValue().length() - a.getValue().length()); + + Map result = new LinkedHashMap<>(); + result.put(FORMAT_VERSION_KEY, FORMAT_VERSION); + + for (Map.Entry entry : attrs.entrySet()) { + if (EXCLUDED_EXPORT_KEYS.contains(entry.getKey())) { + continue; + } + + Object val = entry.getValue(); + if (!(val instanceof String)) { + result.put(entry.getKey(), val); + continue; + } + + String newValue = (String) val; + for (Map.Entry pathEntry : sortedPaths) { + String variable = VARIABLE_PREFIX + pathEntry.getKey() + VARIABLE_SUFFIX; + while (newValue.contains(pathEntry.getValue())) { + newValue = newValue.replace(pathEntry.getValue(), variable); + } + } + result.put(entry.getKey(), newValue); + } + + return result; + } + + public static List findVariables(Map attrs) { + Set variables = new LinkedHashSet<>(); + for (Object val : attrs.values()) { + if (!(val instanceof String)) { + continue; + } + Matcher matcher = VARIABLE_PATTERN.matcher((String) val); + while (matcher.find()) { + variables.add(matcher.group(1)); + } + } + return new ArrayList<>(variables); + } + + public static Map resolveVariables( + Map attrs, Map values) { + Map result = new LinkedHashMap<>(); + for (Map.Entry entry : attrs.entrySet()) { + if (FORMAT_VERSION_KEY.equals(entry.getKey())) { + continue; + } + + Object val = entry.getValue(); + if (!(val instanceof String)) { + result.put(entry.getKey(), val); + continue; + } + + String newValue = (String) val; + for (Map.Entry varEntry : values.entrySet()) { + String variable = VARIABLE_PREFIX + varEntry.getKey() + VARIABLE_SUFFIX; + while (newValue.contains(variable)) { + newValue = newValue.replace(variable, varEntry.getValue()); + } + } + result.put(entry.getKey(), newValue); + } + return result; + } + +} diff --git a/client/jars/org.jboss.tools.rsp.client.cli/src/main/java/org/jboss/tools/rsp/client/cli/StandardCommandHandler.java b/client/jars/org.jboss.tools.rsp.client.cli/src/main/java/org/jboss/tools/rsp/client/cli/StandardCommandHandler.java index 06d0ed2e7..e5968298f 100644 --- a/client/jars/org.jboss.tools.rsp.client.cli/src/main/java/org/jboss/tools/rsp/client/cli/StandardCommandHandler.java +++ b/client/jars/org.jboss.tools.rsp.client.cli/src/main/java/org/jboss/tools/rsp/client/cli/StandardCommandHandler.java @@ -9,16 +9,26 @@ package org.jboss.tools.rsp.client.cli; import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.text.MessageFormat; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + import org.jboss.tools.rsp.api.ServerManagementAPIConstants; import org.jboss.tools.rsp.api.dao.Attribute; import org.jboss.tools.rsp.api.dao.Attributes; @@ -497,6 +507,139 @@ public void execute(String command, ServerManagementClientLauncher launcher, Pro } } }, + EXPORT_SERVER("export server") { + @Override + public void execute(String command, ServerManagementClientLauncher launcher, PromptAssistant assistant) throws Exception { + ServerHandle sh = assistant.selectServer(); + if (sh == null) { + System.out.println("No server selected."); + return; + } + + GetServerJsonResponse resp = launcher.getServerProxy().getServerAsJson(sh).get(); + if (!resp.getStatus().isOK()) { + System.out.println("Error: " + resp.getStatus().getMessage()); + return; + } + + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + java.lang.reflect.Type mapType = new TypeToken>() {}.getType(); + Map attrs = gson.fromJson(resp.getServerJson(), mapType); + + Map exported = ServerDescriptorUtil.substitutePathsForExport(attrs); + + String defaultName = sh.getId().replaceAll("[^a-zA-Z0-9_.-]", "_") + ".server.json"; + System.out.println("Enter file path to export to [" + defaultName + "]: "); + String filePath = assistant.nextLine().trim(); + if (filePath.isEmpty()) { + filePath = defaultName; + } + if (filePath.startsWith("~")) { + filePath = System.getProperty("user.home") + filePath.substring(1); + } + + String content = gson.toJson(exported); + Files.write(Paths.get(filePath), content.getBytes(StandardCharsets.UTF_8)); + System.out.println("Server descriptor exported to " + filePath); + } + }, + + IMPORT_SERVER("import server") { + @Override + public void execute(String command, ServerManagementClientLauncher launcher, PromptAssistant assistant) throws Exception { + System.out.println("Enter path to server descriptor file: "); + String filePath = assistant.nextLine().trim(); + if (filePath.isEmpty()) { + System.out.println("No file specified."); + return; + } + if (filePath.startsWith("~")) { + filePath = System.getProperty("user.home") + filePath.substring(1); + } + + Path path = Paths.get(filePath); + if (!Files.exists(path)) { + System.out.println("File not found: " + filePath); + return; + } + + String content = new String(Files.readAllBytes(path), StandardCharsets.UTF_8); + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + java.lang.reflect.Type mapType = new TypeToken>() {}.getType(); + Map attrs = gson.fromJson(content, mapType); + + String serverTypeId = (String) attrs.get(ServerDescriptorUtil.TYPE_ID_KEY); + if (serverTypeId == null) { + System.out.println("Descriptor file does not contain a server type ID."); + return; + } + + List serverTypes = launcher.getServerProxy().getServerTypes().get(); + ServerType serverType = null; + for (ServerType st : serverTypes) { + if (st.getId().equals(serverTypeId)) { + serverType = st; + break; + } + } + if (serverType == null) { + System.out.println("Server type \"" + serverTypeId + "\" not supported by this RSP."); + return; + } + + List variables = ServerDescriptorUtil.findVariables(attrs); + Map variableValues = new HashMap<>(); + if (!variables.isEmpty()) { + System.out.println("The descriptor contains " + variables.size() + " path variable(s) that need local values:"); + for (String varKey : variables) { + System.out.println("Enter local path for '" + varKey + "': "); + String val = assistant.nextLine().trim(); + if (val.startsWith("~")) { + val = System.getProperty("user.home") + val.substring(1); + } + if (val.isEmpty()) { + System.out.println("Path cannot be empty. Aborting import."); + return; + } + variableValues.put(varKey, val); + } + } + + Map resolved = ServerDescriptorUtil.resolveVariables(attrs, variableValues); + + System.out.println("Enter a name for the new server: "); + String serverName = assistant.nextLine().trim(); + if (serverName.isEmpty()) { + System.out.println("Server name cannot be empty."); + return; + } + + Map serverAttrs = new LinkedHashMap<>(); + for (Map.Entry entry : resolved.entrySet()) { + String key = entry.getKey(); + if (ServerDescriptorUtil.TYPE_ID_KEY.equals(key) + || "id".equals(key) + || ServerDescriptorUtil.FORMAT_VERSION_KEY.equals(key)) { + continue; + } + Object val = entry.getValue(); + if (val instanceof Boolean || val instanceof Number) { + serverAttrs.put(key, String.valueOf(val)); + } else { + serverAttrs.put(key, val); + } + } + + ServerAttributes csa = new ServerAttributes(serverTypeId, serverName, serverAttrs); + CreateServerResponse result = launcher.getServerProxy().createServer(csa).get(); + if (result.getStatus().isOK()) { + System.out.println("Server \"" + serverName + "\" created from descriptor."); + } else { + System.out.println("Error creating server: " + result.getStatus().getMessage()); + } + } + }, + LIST_DEPLOYMENTS("list deployments") { @Override public void execute(String command, ServerManagementClientLauncher launcher, PromptAssistant assistant) {