From cc37ad4c5b40760aa649fc244e54c96c7c5aaabf Mon Sep 17 00:00:00 2001 From: Simon Brown Date: Thu, 22 Feb 2024 12:04:43 +0000 Subject: [PATCH] Updates dependencies, adds an inspect command. --- build.gradle | 16 +- .../com/structurizr/cli/AbstractCommand.java | 26 ++++ .../java/com/structurizr/cli/HelpCommand.java | 2 +- .../com/structurizr/cli/InspectCommand.java | 145 ++++++++++++++++++ .../cli/StructurizrCliApplication.java | 4 +- .../structurizr/cli/export/ExportCommand.java | 25 --- 6 files changed, 184 insertions(+), 34 deletions(-) create mode 100644 src/main/java/com/structurizr/cli/InspectCommand.java diff --git a/build.gradle b/build.gradle index e34fadb..3219658 100644 --- a/build.gradle +++ b/build.gradle @@ -13,14 +13,16 @@ targetCompatibility = 17 repositories { mavenCentral() +// mavenLocal() } dependencies { - implementation 'com.structurizr:structurizr-dsl:1.35.0' - implementation 'com.structurizr:structurizr-export:1.19.0' + implementation 'com.structurizr:structurizr-dsl:2.0.0' + implementation 'com.structurizr:structurizr-export:2.0.0' implementation 'io.github.goto1134:structurizr-d2-exporter:1.5.2' - implementation 'com.structurizr:structurizr-graphviz:2.2.2' + implementation 'com.structurizr:structurizr-autolayout:2.0.0' + implementation 'com.structurizr:structurizr-inspection:2.0.0' implementation 'commons-cli:commons-cli:1.5.0' @@ -29,10 +31,10 @@ dependencies { implementation 'org.jruby:jruby-core:9.4.4.0' implementation 'commons-logging:commons-logging:1.2' - implementation 'org.apache.logging.log4j:log4j-api:2.20.0' - implementation 'org.apache.logging.log4j:log4j-core:2.20.0' - implementation 'org.apache.logging.log4j:log4j-jcl:2.20.0' - implementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.20.0' + implementation 'org.apache.logging.log4j:log4j-api:2.23.0' + implementation 'org.apache.logging.log4j:log4j-core:2.23.0' + implementation 'org.apache.logging.log4j:log4j-jcl:2.23.0' + implementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.23.0' testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.2' diff --git a/src/main/java/com/structurizr/cli/AbstractCommand.java b/src/main/java/com/structurizr/cli/AbstractCommand.java index f71db6d..6192b5b 100644 --- a/src/main/java/com/structurizr/cli/AbstractCommand.java +++ b/src/main/java/com/structurizr/cli/AbstractCommand.java @@ -14,12 +14,15 @@ import org.apache.hc.core5.http.io.entity.EntityUtils; import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; import java.nio.charset.Charset; public abstract class AbstractCommand { private static final Log log = LogFactory.getLog(AbstractCommand.class); + private static final String PLUGINS_DIRECTORY_NAME = "plugins"; private static final int HTTP_OK_STATUS = 200; protected AbstractCommand() { @@ -112,4 +115,27 @@ protected String readFromUrl(String url) { return ""; } + protected Class loadClass(String fqn, File workspaceFile) throws Exception { + File pluginsDirectory = new File(workspaceFile.getParent(), PLUGINS_DIRECTORY_NAME); + URL[] urls = new URL[0]; + + if (pluginsDirectory.exists()) { + File[] jarFiles = pluginsDirectory.listFiles((dir, name) -> name.endsWith(".jar")); + if (jarFiles != null) { + urls = new URL[jarFiles.length]; + for (int i = 0; i < jarFiles.length; i++) { + System.out.println(jarFiles[i].getAbsolutePath()); + try { + urls[i] = jarFiles[i].toURI().toURL(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + + URLClassLoader childClassLoader = new URLClassLoader(urls, getClass().getClassLoader()); + return childClassLoader.loadClass(fqn); + } + } \ No newline at end of file diff --git a/src/main/java/com/structurizr/cli/HelpCommand.java b/src/main/java/com/structurizr/cli/HelpCommand.java index ebc6f91..a9a6065 100644 --- a/src/main/java/com/structurizr/cli/HelpCommand.java +++ b/src/main/java/com/structurizr/cli/HelpCommand.java @@ -11,7 +11,7 @@ class HelpCommand extends AbstractCommand { } public void run(String... args) throws Exception { - log.info("Usage: structurizr push|pull|lock|unlock|export|validate|list|version|help [options]"); + log.info("Usage: structurizr push|pull|lock|unlock|export|validate|inspect|list|version|help [options]"); } } \ No newline at end of file diff --git a/src/main/java/com/structurizr/cli/InspectCommand.java b/src/main/java/com/structurizr/cli/InspectCommand.java new file mode 100644 index 0000000..b086576 --- /dev/null +++ b/src/main/java/com/structurizr/cli/InspectCommand.java @@ -0,0 +1,145 @@ +package com.structurizr.cli; + +import com.structurizr.Workspace; +import com.structurizr.inspection.Inspector; +import com.structurizr.inspection.Severity; +import com.structurizr.inspection.Violation; +import com.structurizr.util.StringUtils; +import org.apache.commons.cli.*; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.File; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +class InspectCommand extends AbstractCommand { + + private static final Log log = LogFactory.getLog(InspectCommand.class); + + private static final String DEFAULT_INSPECTOR = "com.structurizr.inspection.DefaultInspector"; + + InspectCommand() { + } + + public void run(String... args) throws Exception { + Options options = new Options(); + + Option option = new Option("w", "workspace", true, "Path or URL to the workspace JSON/DSL file"); + option.setRequired(true); + options.addOption(option); + + option = new Option("i", "inspector", true, "Inspector implementation to use"); + option.setRequired(false); + options.addOption(option); + + option = new Option("s", "severity", true, "A comma separated list of the severity level(s) to show"); + option.setRequired(false); + options.addOption(option); + + CommandLineParser commandLineParser = new DefaultParser(); + HelpFormatter formatter = new HelpFormatter(); + + String workspacePathAsString = null; + String inspectorName = null; + String severitiesAsString = null; + + try { + CommandLine cmd = commandLineParser.parse(options, args); + + workspacePathAsString = cmd.getOptionValue("workspace"); + inspectorName = cmd.getOptionValue("inspector"); + severitiesAsString = cmd.getOptionValue("severity"); + } catch (ParseException e) { + log.error(e.getMessage()); + formatter.printHelp("inspect", options); + + System.exit(1); + } + + if (StringUtils.isNullOrEmpty(inspectorName)) { + inspectorName = DEFAULT_INSPECTOR; + } + + log.debug("Inspecting workspace at " + workspacePathAsString + " using " + inspectorName); + + Set severities = new HashSet<>(); + if (StringUtils.isNullOrEmpty(severitiesAsString)) { + severities.add(Severity.ERROR); + severities.add(Severity.WARNING); + severities.add(Severity.INFO); + severities.add(Severity.IGNORE); + } else { + for (String severity : severitiesAsString.split(",")) { + severities.add(Severity.valueOf(severity.trim().toUpperCase())); + } + } + + try { + Workspace workspace = loadWorkspace(workspacePathAsString); + Inspector inspector = findInspector(inspectorName, workspace, new File(workspacePathAsString)); + + if (inspector != null) { + List violations = inspector.getViolations(); + violations.sort(Comparator.comparing(Violation::getSeverity)); + + violations = violations.stream().filter(v -> severities.contains(v.getSeverity())).collect(Collectors.toList()); + + if (!violations.isEmpty()) { + int typeColumns = 0; + int descriptionColumns = 0; + for (Violation violation : violations) { + typeColumns = Math.max(typeColumns, violation.getType().length()); + descriptionColumns = Math.max(descriptionColumns, violation.getMessage().length()); + } + + String rowFormat = "%-6s | %-" + typeColumns + "s | %s"; + + int counter = 0; + for (Violation violation : violations) { + if (severities.contains(violation.getSeverity())) { + counter++; + + String line = String.format( + rowFormat, + violation.getSeverity().toString(), + violation.getType(), + violation.getMessage() + ); + + log.info(line); + } + } + + System.exit(counter); // non-zero if there are violations shown + } + } + } catch (Exception e) { + // print the error and exit + log.error(e.getMessage()); + System.exit(1); + } + + log.debug(" - inspected"); + log.debug(" - finished"); + } + + private Inspector findInspector(String name, Workspace workspace, File workspacePath) { + try { + Class clazz = loadClass(name, workspacePath); + if (Inspector.class.isAssignableFrom(clazz)) { + return (Inspector) clazz.getDeclaredConstructor(Workspace.class).newInstance(workspace); + } + } catch (ClassNotFoundException e) { + log.error(" - unknown inspector: " + name); + } catch (Exception e) { + log.error(" - error creating instance of " + name, e); + } + + return null; + } + +} \ No newline at end of file diff --git a/src/main/java/com/structurizr/cli/StructurizrCliApplication.java b/src/main/java/com/structurizr/cli/StructurizrCliApplication.java index 5f85f4f..b32f7f5 100644 --- a/src/main/java/com/structurizr/cli/StructurizrCliApplication.java +++ b/src/main/java/com/structurizr/cli/StructurizrCliApplication.java @@ -24,6 +24,7 @@ public class StructurizrCliApplication { private static final String EXPORT_COMMAND = "export"; private static final String MERGE_COMMAND = "merge"; private static final String VALIDATE_COMMAND = "validate"; + private static final String INSPECT_COMMAND = "inspect"; private static final String LIST_COMMAND = "list"; private static final String VERSION_COMMAND = "version"; private static final String HELP_COMMAND = "help"; @@ -64,6 +65,7 @@ public class StructurizrCliApplication { COMMANDS.put(EXPORT_COMMAND, new ExportCommand()); COMMANDS.put(MERGE_COMMAND, new MergeCommand()); COMMANDS.put(VALIDATE_COMMAND, new ValidateCommand()); + COMMANDS.put(INSPECT_COMMAND, new InspectCommand()); COMMANDS.put(LIST_COMMAND, new ListCommand()); COMMANDS.put(VERSION_COMMAND, new VersionCommand()); COMMANDS.put(HELP_COMMAND, new HelpCommand()); @@ -94,7 +96,7 @@ private void printUsageMessageAndExit(String commandName) { log.error("Error: " + commandName + " not recognised"); } - log.error("Usage: structurizr push|pull|lock|unlock|export|validate|list|version|help [options]"); + log.error("Usage: structurizr push|pull|lock|unlock|export|validate|inspect|list|version|help [options]"); System.exit(1); } diff --git a/src/main/java/com/structurizr/cli/export/ExportCommand.java b/src/main/java/com/structurizr/cli/export/ExportCommand.java index 032575f..50a559e 100644 --- a/src/main/java/com/structurizr/cli/export/ExportCommand.java +++ b/src/main/java/com/structurizr/cli/export/ExportCommand.java @@ -29,8 +29,6 @@ public class ExportCommand extends AbstractCommand { - private static final String PLUGINS_DIRECTORY_NAME = "plugins"; - private static final Log log = LogFactory.getLog(ExportCommand.class); private static final String JSON_FORMAT = "json"; @@ -199,29 +197,6 @@ private Exporter findExporter(String format, File workspacePath) { return null; } - private Class loadClass(String fqn, File workspaceFile) throws Exception { - File pluginsDirectory = new File(workspaceFile.getParent(), PLUGINS_DIRECTORY_NAME); - URL[] urls = new URL[0]; - - if (pluginsDirectory.exists()) { - File[] jarFiles = pluginsDirectory.listFiles((dir, name) -> name.endsWith(".jar")); - if (jarFiles != null) { - urls = new URL[jarFiles.length]; - for (int i = 0; i < jarFiles.length; i++) { - System.out.println(jarFiles[i].getAbsolutePath()); - try { - urls[i] = jarFiles[i].toURI().toURL(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - } - - URLClassLoader childClassLoader = new URLClassLoader(urls, getClass().getClassLoader()); - return childClassLoader.loadClass(fqn); - } - private String prefix(long workspaceId) { if (workspaceId > 0) { return "structurizr-" + workspaceId;