From 4f79f2d87652a34c1a1291933a4f8e20d7e5b499 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Fri, 8 Nov 2024 09:49:11 +0100 Subject: [PATCH] [MNG-8332] Detach CLIng from deprecated Embedder (#1882) Detach maven-cli from maven-embedder (deprecated). Basically, classes are copied from embedder to cli, while embedder points to old classes and cli to new classes. **One important exception is logging**: the classes are _moved_ to cli, and embedder modified to use those classes (and dependency reversed: embedder now depends on cli). Reason is Verifier, that still calls into "hack method" of deprecated MavenCli, and then without logging classes move would explode due casting. --- https://issues.apache.org/jira/browse/MNG-8332 --- compat/maven-embedder/pom.xml | 34 +- .../java/org/apache/maven/cli/CLIManager.java | 1 + .../apache/maven/cli/CLIReportingUtils.java | 1 + .../org/apache/maven/cli/CleanArgument.java | 1 + .../java/org/apache/maven/cli/CliRequest.java | 1 + .../cli/ExtensionConfigurationModule.java | 1 + .../java/org/apache/maven/cli/MavenCli.java | 9 +- .../org/apache/maven/cli/ResolveFile.java | 1 + .../configuration/ConfigurationProcessor.java | 1 + .../SettingsXmlConfigurationProcessor.java | 1 + .../cli/event/DefaultEventSpyContext.java | 1 + .../maven/cli/event/ExecutionEventLogger.java | 1 + .../BootstrapCoreExtensionManager.java | 1 + .../ExtensionResolutionException.java | 1 + .../maven/cli/logging/Slf4jStdoutLogger.java | 1 + .../maven/cli/props/MavenProperties.java | 1 + .../cli/props/MavenPropertiesLoader.java | 1 + .../AbstractMavenTransferListener.java | 1 + .../ConsoleMavenTransferListener.java | 1 + .../maven/cli/transfer/FileSizeFormat.java | 1 + .../transfer/QuietMavenTransferListener.java | 1 + .../cli/transfer/SimplexTransferListener.java | 1 + .../transfer/Slf4jMavenTransferListener.java | 1 + .../transfer/TransferResourceIdentifier.java | 1 + .../sisu/plexus/PlexusXmlBeanConverter.java | 1 + .../main/java/org/fusesource/jansi/Ansi.java | 1 + impl/maven-cli/pom.xml | 34 +- .../cling/event/ExecutionEventLogger.java | 479 +++++++ .../BootstrapCoreExtensionManager.java | 283 ++++ .../ExtensionConfigurationModule.java | 75 ++ .../ExtensionResolutionException.java | 42 + .../maven/cling/invoker/BaseParser.java | 6 +- .../maven/cling/invoker/CleanArgument.java | 103 ++ .../cling/invoker/CommonsCliOptions.java | 1 - .../maven/cling/invoker/LookupInvoker.java | 14 +- .../PlexusContainerCapsuleFactory.java | 6 +- .../org/apache/maven/cling/invoker/Utils.java | 2 +- .../invoker/mvn/DefaultMavenInvoker.java | 4 +- .../invoker/mvnenc/DefaultEncryptInvoker.java | 2 +- .../logging/BaseSlf4jConfiguration.java | 4 +- .../cling}/logging/Slf4jConfiguration.java | 2 +- .../logging/Slf4jConfigurationFactory.java | 15 +- .../maven/cling}/logging/Slf4jLogger.java | 25 +- .../cling}/logging/Slf4jLoggerManager.java | 16 +- .../logging/impl/Log4j2Configuration.java | 4 +- .../logging/impl/LogbackConfiguration.java | 4 +- .../impl/MavenSimpleConfiguration.java | 4 +- .../UnsupportedSlf4jBindingConfiguration.java | 4 +- .../maven/cling/props/MavenProperties.java | 1153 +++++++++++++++++ .../cling/props/MavenPropertiesLoader.java | 167 +++ .../AbstractMavenTransferListener.java | 99 ++ .../ConsoleMavenTransferListener.java | 157 +++ .../maven/cling/transfer/FileSizeFormat.java | 204 +++ .../transfer/QuietMavenTransferListener.java | 25 + .../transfer/SimplexTransferListener.java | 230 ++++ .../transfer/Slf4jMavenTransferListener.java | 97 ++ .../transfer/TransferResourceIdentifier.java | 35 + .../maven/cling/utils/CLIReportingUtils.java | 203 +++ .../maven/slf4j-configuration.properties | 8 +- 59 files changed, 3497 insertions(+), 77 deletions(-) create mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/event/ExecutionEventLogger.java create mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/BootstrapCoreExtensionManager.java create mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/ExtensionConfigurationModule.java create mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/ExtensionResolutionException.java create mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/CleanArgument.java rename {compat/maven-embedder/src/main/java/org/apache/maven/cli => impl/maven-cli/src/main/java/org/apache/maven/cling}/logging/BaseSlf4jConfiguration.java (95%) rename {compat/maven-embedder/src/main/java/org/apache/maven/cli => impl/maven-cli/src/main/java/org/apache/maven/cling}/logging/Slf4jConfiguration.java (97%) rename {compat/maven-embedder/src/main/java/org/apache/maven/cli => impl/maven-cli/src/main/java/org/apache/maven/cling}/logging/Slf4jConfigurationFactory.java (80%) rename {compat/maven-embedder/src/main/java/org/apache/maven/cli => impl/maven-cli/src/main/java/org/apache/maven/cling}/logging/Slf4jLogger.java (90%) rename {compat/maven-embedder/src/main/java/org/apache/maven/cli => impl/maven-cli/src/main/java/org/apache/maven/cling}/logging/Slf4jLoggerManager.java (90%) rename {compat/maven-embedder/src/main/java/org/apache/maven/cli => impl/maven-cli/src/main/java/org/apache/maven/cling}/logging/impl/Log4j2Configuration.java (93%) rename {compat/maven-embedder/src/main/java/org/apache/maven/cli => impl/maven-cli/src/main/java/org/apache/maven/cling}/logging/impl/LogbackConfiguration.java (93%) rename {compat/maven-embedder/src/main/java/org/apache/maven/cli => impl/maven-cli/src/main/java/org/apache/maven/cling}/logging/impl/MavenSimpleConfiguration.java (94%) rename {compat/maven-embedder/src/main/java/org/apache/maven/cli => impl/maven-cli/src/main/java/org/apache/maven/cling}/logging/impl/UnsupportedSlf4jBindingConfiguration.java (92%) create mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/props/MavenProperties.java create mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/props/MavenPropertiesLoader.java create mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/AbstractMavenTransferListener.java create mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/ConsoleMavenTransferListener.java create mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/FileSizeFormat.java create mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/QuietMavenTransferListener.java create mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/SimplexTransferListener.java create mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/Slf4jMavenTransferListener.java create mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/TransferResourceIdentifier.java create mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/utils/CLIReportingUtils.java rename {compat/maven-embedder => impl/maven-cli}/src/main/resources/META-INF/maven/slf4j-configuration.properties (74%) diff --git a/compat/maven-embedder/pom.xml b/compat/maven-embedder/pom.xml index 1970fff6ad..f83a552445 100644 --- a/compat/maven-embedder/pom.xml +++ b/compat/maven-embedder/pom.xml @@ -30,7 +30,7 @@ under the License. maven-embedder - Maven Embedder + Maven Embedder (deprecated) Maven embeddable component, with CLI and logging support. @@ -38,6 +38,10 @@ under the License. org.apache.maven maven-api-cli + + org.apache.maven + maven-cli + org.apache.maven maven-settings @@ -183,34 +187,6 @@ under the License. org.eclipse.sisu sisu-maven-plugin - - org.codehaus.modello - modello-maven-plugin - - 1.2.0 - - ../../api/maven-api-cli/src/main/mdo/core-extensions.mdo - - - - - - - packageModelV4=org.apache.maven.api.cli.extensions - packageToolV4=org.apache.maven.cli.internal.extension.io - - ${project.basedir}/../../src/mdo - - - - modello - - velocity - xsd - - - - diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java index 8312f3bd87..3cc0d38a67 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java +++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/CLIManager.java @@ -34,6 +34,7 @@ import org.apache.maven.jline.MessageUtils; /** */ +@Deprecated public class CLIManager { public static final char ALTERNATE_POM_FILE = 'f'; diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/CLIReportingUtils.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/CLIReportingUtils.java index 21d2c63d3d..40005dcc5d 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/CLIReportingUtils.java +++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/CLIReportingUtils.java @@ -33,6 +33,7 @@ import org.slf4j.Logger; * Utility class used to report errors, statistics, application version info, etc. * */ +@Deprecated public final class CLIReportingUtils { // CHECKSTYLE_OFF: MagicNumber public static final long MB = 1024 * 1024; diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/CleanArgument.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/CleanArgument.java index 4bd7f6aacb..4c8d1e93c8 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/CleanArgument.java +++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/CleanArgument.java @@ -24,6 +24,7 @@ import java.util.List; /** * CleanArgument */ +@Deprecated public class CleanArgument { public static String[] cleanArgs(String[] args) { try { diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/CliRequest.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/CliRequest.java index 1e8a184d56..6d54be4ce0 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/CliRequest.java +++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/CliRequest.java @@ -30,6 +30,7 @@ import org.codehaus.plexus.classworlds.ClassWorld; /** * CliRequest */ +@Deprecated public class CliRequest { String[] args; diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/ExtensionConfigurationModule.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/ExtensionConfigurationModule.java index aa40249194..b0f4b9e9ba 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/ExtensionConfigurationModule.java +++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/ExtensionConfigurationModule.java @@ -32,6 +32,7 @@ import org.apache.maven.internal.xml.XmlPlexusConfiguration; import org.apache.maven.model.v4.MavenTransformer; import org.codehaus.plexus.configuration.PlexusConfiguration; +@Deprecated public class ExtensionConfigurationModule implements Module { private final CoreExtensionEntry extension; diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java index 2a11634602..70388bf326 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java +++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java @@ -70,16 +70,16 @@ import org.apache.maven.cli.configuration.SettingsXmlConfigurationProcessor; import org.apache.maven.cli.event.DefaultEventSpyContext; import org.apache.maven.cli.event.ExecutionEventLogger; import org.apache.maven.cli.internal.BootstrapCoreExtensionManager; -import org.apache.maven.cli.internal.extension.io.CoreExtensionsStaxReader; -import org.apache.maven.cli.logging.Slf4jConfiguration; -import org.apache.maven.cli.logging.Slf4jConfigurationFactory; -import org.apache.maven.cli.logging.Slf4jLoggerManager; import org.apache.maven.cli.logging.Slf4jStdoutLogger; import org.apache.maven.cli.props.MavenPropertiesLoader; import org.apache.maven.cli.transfer.ConsoleMavenTransferListener; import org.apache.maven.cli.transfer.QuietMavenTransferListener; import org.apache.maven.cli.transfer.SimplexTransferListener; import org.apache.maven.cli.transfer.Slf4jMavenTransferListener; +import org.apache.maven.cling.internal.extension.io.CoreExtensionsStaxReader; +import org.apache.maven.cling.logging.Slf4jConfiguration; +import org.apache.maven.cling.logging.Slf4jConfigurationFactory; +import org.apache.maven.cling.logging.Slf4jLoggerManager; import org.apache.maven.di.Injector; import org.apache.maven.eventspy.internal.EventSpyDispatcher; import org.apache.maven.exception.DefaultExceptionHandler; @@ -141,6 +141,7 @@ import static org.apache.maven.cli.ResolveFile.resolveFile; /** */ +@Deprecated public class MavenCli { public static final String MULTIMODULE_PROJECT_DIRECTORY = "maven.multiModuleProjectDirectory"; diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/ResolveFile.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/ResolveFile.java index d4d496f0c8..16942d0b00 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/ResolveFile.java +++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/ResolveFile.java @@ -24,6 +24,7 @@ import java.nio.file.Paths; /** * Resolve relative file path against the given base directory */ +@Deprecated public class ResolveFile { public static File resolveFile(File file, String baseDirectory) { if (file == null) { diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/configuration/ConfigurationProcessor.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/configuration/ConfigurationProcessor.java index 6e9f064f14..916b8737f6 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/configuration/ConfigurationProcessor.java +++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/configuration/ConfigurationProcessor.java @@ -23,6 +23,7 @@ import org.apache.maven.cli.CliRequest; /** * ConfigurationProcessor */ +@Deprecated public interface ConfigurationProcessor { void process(CliRequest request) throws Exception; } diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/configuration/SettingsXmlConfigurationProcessor.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/configuration/SettingsXmlConfigurationProcessor.java index a318779e70..199c6e0b52 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/configuration/SettingsXmlConfigurationProcessor.java +++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/configuration/SettingsXmlConfigurationProcessor.java @@ -58,6 +58,7 @@ import static org.apache.maven.cli.ResolveFile.resolveFile; */ @Named(SettingsXmlConfigurationProcessor.HINT) @Singleton +@Deprecated public class SettingsXmlConfigurationProcessor implements ConfigurationProcessor { public static final String HINT = "settings"; diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/event/DefaultEventSpyContext.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/event/DefaultEventSpyContext.java index b3c7671b46..d241199407 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/event/DefaultEventSpyContext.java +++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/event/DefaultEventSpyContext.java @@ -26,6 +26,7 @@ import org.apache.maven.eventspy.EventSpy; /** * DefaultEventSpyContext */ +@Deprecated public class DefaultEventSpyContext implements EventSpy.Context { private final Map data = new HashMap<>(); diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/event/ExecutionEventLogger.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/event/ExecutionEventLogger.java index e53e74bda6..2aea29d6cb 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/event/ExecutionEventLogger.java +++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/event/ExecutionEventLogger.java @@ -46,6 +46,7 @@ import static org.apache.maven.cli.CLIReportingUtils.formatTimestamp; * Logs execution events to logger, eventually user-supplied. * */ +@Deprecated public class ExecutionEventLogger extends AbstractExecutionListener { private static final int MAX_LOG_PREFIX_SIZE = 8; // "[ERROR] " private static final int PROJECT_STATUS_SUFFIX_SIZE = 20; // "SUCCESS [ 0.000 s]" diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/BootstrapCoreExtensionManager.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/BootstrapCoreExtensionManager.java index 90f0349e8a..a19c0f81d7 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/BootstrapCoreExtensionManager.java +++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/BootstrapCoreExtensionManager.java @@ -87,6 +87,7 @@ import org.slf4j.LoggerFactory; /** * BootstrapCoreExtensionManager */ +@Deprecated @Named public class BootstrapCoreExtensionManager { public static final String STRATEGY_PARENT_FIRST = "parent-first"; diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/ExtensionResolutionException.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/ExtensionResolutionException.java index 9683c0579f..56a601901f 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/ExtensionResolutionException.java +++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/internal/ExtensionResolutionException.java @@ -24,6 +24,7 @@ import org.apache.maven.api.cli.extensions.CoreExtension; * Exception occurring trying to resolve a plugin. * */ +@Deprecated public class ExtensionResolutionException extends Exception { private final CoreExtension extension; diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/Slf4jStdoutLogger.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/Slf4jStdoutLogger.java index 1d7a63c0b1..7433436dcb 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/Slf4jStdoutLogger.java +++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/Slf4jStdoutLogger.java @@ -28,6 +28,7 @@ import static java.util.Objects.requireNonNull; /** * @since 3.1.0 */ +@Deprecated public class Slf4jStdoutLogger implements Logger { private static final String ERROR = "[ERROR] "; diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/props/MavenProperties.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/props/MavenProperties.java index bf5deaa0cd..99df049210 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/props/MavenProperties.java +++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/props/MavenProperties.java @@ -50,6 +50,7 @@ import org.apache.maven.internal.impl.model.DefaultInterpolator; * Enhancement of the standard Properties * managing the maintain of comments, etc. */ +@Deprecated public class MavenProperties extends AbstractMap { /** Constant for the supported comment characters.*/ diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/props/MavenPropertiesLoader.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/props/MavenPropertiesLoader.java index 8b7b207efb..d83f56850f 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/props/MavenPropertiesLoader.java +++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/props/MavenPropertiesLoader.java @@ -28,6 +28,7 @@ import java.util.function.Function; import org.apache.maven.internal.impl.model.DefaultInterpolator; +@Deprecated public class MavenPropertiesLoader { public static final String INCLUDES_PROPERTY = "${includes}"; // includes diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/AbstractMavenTransferListener.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/AbstractMavenTransferListener.java index 0afa533fd1..e678e62994 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/AbstractMavenTransferListener.java +++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/AbstractMavenTransferListener.java @@ -31,6 +31,7 @@ import org.eclipse.aether.transfer.TransferResource; /** * AbstractMavenTransferListener */ +@Deprecated public abstract class AbstractMavenTransferListener extends AbstractTransferListener { public static final String STYLE = ".transfer:-faint"; diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/ConsoleMavenTransferListener.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/ConsoleMavenTransferListener.java index 0331d27e86..c03d001491 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/ConsoleMavenTransferListener.java +++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/ConsoleMavenTransferListener.java @@ -34,6 +34,7 @@ import org.eclipse.aether.transfer.TransferResource; *

* This listener is not thread-safe and should be wrapped in the {@link SimplexTransferListener} in a multi-threaded scenario. */ +@Deprecated public class ConsoleMavenTransferListener extends AbstractMavenTransferListener { private final Map transfers = new LinkedHashMap<>(); diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/FileSizeFormat.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/FileSizeFormat.java index 29ff04af58..caf977b1e5 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/FileSizeFormat.java +++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/FileSizeFormat.java @@ -30,6 +30,7 @@ import org.apache.maven.api.services.MessageBuilder; * @see https://en.wikipedia.org/wiki/Octet_(computing) */ +@Deprecated public class FileSizeFormat { public enum ScaleUnit { BYTE { diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/QuietMavenTransferListener.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/QuietMavenTransferListener.java index 400ad13702..f1aa4d9230 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/QuietMavenTransferListener.java +++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/QuietMavenTransferListener.java @@ -22,4 +22,5 @@ import org.eclipse.aether.transfer.AbstractTransferListener; /** */ +@Deprecated public class QuietMavenTransferListener extends AbstractTransferListener {} diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/SimplexTransferListener.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/SimplexTransferListener.java index 8353fa020b..ae1e736786 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/SimplexTransferListener.java +++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/SimplexTransferListener.java @@ -42,6 +42,7 @@ import static java.util.Objects.requireNonNull; * * @since 4.0.0 */ +@Deprecated public final class SimplexTransferListener extends AbstractTransferListener { private static final Logger LOGGER = LoggerFactory.getLogger(SimplexTransferListener.class); private static final int QUEUE_SIZE = 1024; diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/Slf4jMavenTransferListener.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/Slf4jMavenTransferListener.java index 7853216db1..d0f4cc57e8 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/Slf4jMavenTransferListener.java +++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/Slf4jMavenTransferListener.java @@ -28,6 +28,7 @@ import org.slf4j.LoggerFactory; /** * Slf4jMavenTransferListener */ +@Deprecated public class Slf4jMavenTransferListener extends AbstractTransferListener { protected final Logger out; diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/TransferResourceIdentifier.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/TransferResourceIdentifier.java index f45139886b..8789b9b1e1 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/TransferResourceIdentifier.java +++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/transfer/TransferResourceIdentifier.java @@ -28,6 +28,7 @@ import org.eclipse.aether.transfer.TransferResource; * The {@link TransferResource} is not immutable and does not implement {@code Objects#equals} and {@code Objects#hashCode} methods, * making it not very suitable for usage in collections. */ +@Deprecated record TransferResourceIdentifier(String repositoryId, String repositoryUrl, String resourceName, @Nullable File file) { TransferResourceIdentifier(TransferResource resource) { this(resource.getRepositoryId(), resource.getRepositoryUrl(), resource.getResourceName(), resource.getFile()); diff --git a/compat/maven-embedder/src/main/java/org/eclipse/sisu/plexus/PlexusXmlBeanConverter.java b/compat/maven-embedder/src/main/java/org/eclipse/sisu/plexus/PlexusXmlBeanConverter.java index 0ed0eedc6a..5f06757069 100644 --- a/compat/maven-embedder/src/main/java/org/eclipse/sisu/plexus/PlexusXmlBeanConverter.java +++ b/compat/maven-embedder/src/main/java/org/eclipse/sisu/plexus/PlexusXmlBeanConverter.java @@ -53,6 +53,7 @@ import org.eclipse.sisu.inject.TypeArguments; */ @Singleton @Priority(10) +@Deprecated public final class PlexusXmlBeanConverter implements PlexusBeanConverter { // ---------------------------------------------------------------------- // Constants diff --git a/compat/maven-embedder/src/main/java/org/fusesource/jansi/Ansi.java b/compat/maven-embedder/src/main/java/org/fusesource/jansi/Ansi.java index d41e251621..4a446b65c4 100644 --- a/compat/maven-embedder/src/main/java/org/fusesource/jansi/Ansi.java +++ b/compat/maven-embedder/src/main/java/org/fusesource/jansi/Ansi.java @@ -28,6 +28,7 @@ import java.util.ArrayList; * with maven-shared-utils, while Maven has migrated to JLine (into which Jansi has been merged * since JLine 3.25.0). */ +@Deprecated @SuppressWarnings("unused") public class Ansi implements Appendable { diff --git a/impl/maven-cli/pom.xml b/impl/maven-cli/pom.xml index 71226ad39d..a34a19eb8b 100644 --- a/impl/maven-cli/pom.xml +++ b/impl/maven-cli/pom.xml @@ -40,7 +40,7 @@ under the License. org.apache.maven - maven-embedder + maven-core org.codehaus.plexus @@ -78,6 +78,11 @@ under the License. commons-cli commons-cli + + ch.qos.logback + logback-classic + true + org.junit.jupiter @@ -98,6 +103,33 @@ under the License. org.eclipse.sisu sisu-maven-plugin + + org.codehaus.modello + modello-maven-plugin + + 1.2.0 + + ../../api/maven-api-cli/src/main/mdo/core-extensions.mdo + + + + + + + packageModelV4=org.apache.maven.api.cli.extensions + packageToolV4=org.apache.maven.cling.internal.extension.io + + ${project.basedir}/../../src/mdo + + + + modello + + velocity + + + + org.apache.maven.plugins maven-jar-plugin diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/event/ExecutionEventLogger.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/event/ExecutionEventLogger.java new file mode 100644 index 0000000000..5c401ba7e0 --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/event/ExecutionEventLogger.java @@ -0,0 +1,479 @@ +/* + * 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.maven.cling.event; + +import java.io.File; +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; + +import org.apache.maven.api.services.MessageBuilder; +import org.apache.maven.api.services.MessageBuilderFactory; +import org.apache.maven.execution.AbstractExecutionListener; +import org.apache.maven.execution.BuildFailure; +import org.apache.maven.execution.BuildSuccess; +import org.apache.maven.execution.BuildSummary; +import org.apache.maven.execution.ExecutionEvent; +import org.apache.maven.execution.MavenExecutionResult; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.plugin.MojoExecution; +import org.apache.maven.plugin.descriptor.MojoDescriptor; +import org.apache.maven.project.MavenProject; +import org.slf4j.ILoggerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.maven.cling.utils.CLIReportingUtils.formatDuration; +import static org.apache.maven.cling.utils.CLIReportingUtils.formatTimestamp; + +/** + * Logs execution events to logger, eventually user-supplied. + * + */ +public class ExecutionEventLogger extends AbstractExecutionListener { + private static final int MAX_LOG_PREFIX_SIZE = 8; // "[ERROR] " + private static final int PROJECT_STATUS_SUFFIX_SIZE = 20; // "SUCCESS [ 0.000 s]" + private static final int MIN_TERMINAL_WIDTH = 60; + private static final int DEFAULT_TERMINAL_WIDTH = 80; + private static final int MAX_TERMINAL_WIDTH = 130; + private static final int MAX_PADDED_BUILD_TIME_DURATION_LENGTH = 9; + + private final MessageBuilderFactory messageBuilderFactory; + private final Logger logger; + private int terminalWidth; + private int lineLength; + private int maxProjectNameLength; + private int totalProjects; + private volatile int currentVisitedProjectCount; + + public ExecutionEventLogger(MessageBuilderFactory messageBuilderFactory) { + this(messageBuilderFactory, LoggerFactory.getLogger(ExecutionEventLogger.class)); + } + + public ExecutionEventLogger(MessageBuilderFactory messageBuilderFactory, Logger logger) { + this(messageBuilderFactory, logger, -1); + } + + public ExecutionEventLogger(MessageBuilderFactory messageBuilderFactory, Logger logger, int terminalWidth) { + this.logger = Objects.requireNonNull(logger, "logger cannot be null"); + this.messageBuilderFactory = messageBuilderFactory; + this.terminalWidth = terminalWidth; + } + + private static String chars(char c, int count) { + return String.valueOf(c).repeat(Math.max(0, count)); + } + + private void infoLine(char c) { + infoMain(chars(c, lineLength)); + } + + private void infoMain(String msg) { + logger.info(builder().strong(msg).toString()); + } + + private void init() { + if (maxProjectNameLength == 0) { + if (terminalWidth < 0) { + terminalWidth = messageBuilderFactory.getTerminalWidth(); + } + terminalWidth = Math.min( + MAX_TERMINAL_WIDTH, + Math.max(terminalWidth <= 0 ? DEFAULT_TERMINAL_WIDTH : terminalWidth, MIN_TERMINAL_WIDTH)); + lineLength = terminalWidth - MAX_LOG_PREFIX_SIZE; + maxProjectNameLength = lineLength - PROJECT_STATUS_SUFFIX_SIZE; + } + } + + @Override + public void projectDiscoveryStarted(ExecutionEvent event) { + if (logger.isInfoEnabled()) { + init(); + logger.info("Scanning for projects..."); + } + } + + @Override + public void sessionStarted(ExecutionEvent event) { + if (logger.isInfoEnabled() && event.getSession().getProjects().size() > 1) { + init(); + infoLine('-'); + + infoMain("Reactor Build Order:"); + + logger.info(""); + + final List projects = event.getSession().getProjects(); + for (MavenProject project : projects) { + int len = lineLength + - project.getName().length() + - project.getPackaging().length() + - 2; + logger.info("{}{}[{}]", project.getName(), chars(' ', (len > 0) ? len : 1), project.getPackaging()); + } + + final List allProjects = event.getSession().getAllProjects(); + + currentVisitedProjectCount = allProjects.size() - projects.size(); + totalProjects = allProjects.size(); + } + } + + @Override + public void sessionEnded(ExecutionEvent event) { + if (logger.isInfoEnabled()) { + init(); + if (event.getSession().getProjects().size() > 1) { + logReactorSummary(event.getSession()); + } + + ILoggerFactory iLoggerFactory = LoggerFactory.getILoggerFactory(); + + if (iLoggerFactory instanceof org.apache.maven.logging.api.LogLevelRecorder recorder + && recorder.hasReachedMaxLevel()) { + event.getSession() + .getResult() + .addException( + new Exception("Build failed due to log statements with a higher severity than allowed. " + + "Fix the logged issues or remove flag --fail-on-severity (-fos).")); + } + + logResult(event.getSession()); + + logStats(event.getSession()); + + infoLine('-'); + } + } + + private boolean isSingleVersionedReactor(MavenSession session) { + boolean result = true; + + MavenProject topProject = session.getTopLevelProject(); + List sortedProjects = session.getProjectDependencyGraph().getSortedProjects(); + for (MavenProject mavenProject : sortedProjects) { + if (!topProject.getVersion().equals(mavenProject.getVersion())) { + result = false; + break; + } + } + + return result; + } + + private void logReactorSummary(MavenSession session) { + boolean isSingleVersion = isSingleVersionedReactor(session); + + infoLine('-'); + + StringBuilder summary = new StringBuilder("Reactor Summary"); + if (isSingleVersion) { + summary.append(" for "); + summary.append(session.getTopLevelProject().getName()); + summary.append(" "); + summary.append(session.getTopLevelProject().getVersion()); + } + summary.append(":"); + infoMain(summary.toString()); + + logger.info(""); + + MavenExecutionResult result = session.getResult(); + + List projects = session.getProjects(); + + StringBuilder buffer = new StringBuilder(128); + + for (MavenProject project : projects) { + buffer.append(project.getName()); + buffer.append(' '); + + if (!isSingleVersion) { + buffer.append(project.getVersion()); + buffer.append(' '); + } + + if (buffer.length() <= maxProjectNameLength) { + while (buffer.length() < maxProjectNameLength) { + buffer.append('.'); + } + buffer.append(' '); + } + + BuildSummary buildSummary = result.getBuildSummary(project); + + if (buildSummary == null) { + buffer.append(builder().warning("SKIPPED")); + } else if (buildSummary instanceof BuildSuccess) { + buffer.append(builder().success("SUCCESS")); + buffer.append(" ["); + String buildTimeDuration = formatDuration(buildSummary.getTime()); + int padSize = MAX_PADDED_BUILD_TIME_DURATION_LENGTH - buildTimeDuration.length(); + if (padSize > 0) { + buffer.append(chars(' ', padSize)); + } + buffer.append(buildTimeDuration); + buffer.append(']'); + } else if (buildSummary instanceof BuildFailure) { + buffer.append(builder().failure("FAILURE")); + buffer.append(" ["); + String buildTimeDuration = formatDuration(buildSummary.getTime()); + int padSize = MAX_PADDED_BUILD_TIME_DURATION_LENGTH - buildTimeDuration.length(); + if (padSize > 0) { + buffer.append(chars(' ', padSize)); + } + buffer.append(buildTimeDuration); + buffer.append(']'); + } + + logger.info(buffer.toString()); + buffer.setLength(0); + } + } + + private void logResult(MavenSession session) { + infoLine('-'); + MessageBuilder buffer = builder(); + + if (session.getResult().hasExceptions()) { + buffer.failure("BUILD FAILURE"); + } else { + buffer.success("BUILD SUCCESS"); + } + logger.info(buffer.toString()); + } + + private MessageBuilder builder() { + return messageBuilderFactory.builder(); + } + + private void logStats(MavenSession session) { + infoLine('-'); + + long finish = System.currentTimeMillis(); + + long time = finish - session.getRequest().getStartTime().getTime(); + + String wallClock = session.getRequest().getDegreeOfConcurrency() > 1 ? " (Wall Clock)" : ""; + + logger.info("Total time: {}{}", formatDuration(time), wallClock); + + logger.info("Finished at: {}", formatTimestamp(finish)); + } + + @Override + public void projectSkipped(ExecutionEvent event) { + if (logger.isInfoEnabled()) { + init(); + logger.info(""); + infoLine('-'); + String name = event.getProject().getName(); + infoMain("Skipping " + name); + logger.info("{} was not built because a module it depends on failed to build.", name); + + infoLine('-'); + } + } + + @Override + public void projectStarted(ExecutionEvent event) { + if (logger.isInfoEnabled()) { + init(); + MavenProject project = event.getProject(); + + logger.info(""); + + // -------< groupId:artifactId >------- + String projectKey = project.getGroupId() + ':' + project.getArtifactId(); + + final String preHeader = "--< "; + final String postHeader = " >--"; + + final int headerLen = preHeader.length() + projectKey.length() + postHeader.length(); + + String prefix = chars('-', Math.max(0, (lineLength - headerLen) / 2)) + preHeader; + + String suffix = + postHeader + chars('-', Math.max(0, lineLength - headerLen - prefix.length() + preHeader.length())); + + logger.info( + builder().strong(prefix).project(projectKey).strong(suffix).toString()); + + // Building Project Name Version [i/n] + String building = "Building " + event.getProject().getName() + " " + + event.getProject().getVersion(); + + if (totalProjects <= 1) { + infoMain(building); + } else { + // display progress [i/n] + int number; + synchronized (this) { + number = ++currentVisitedProjectCount; + } + String progress = " [" + number + '/' + totalProjects + ']'; + + int pad = lineLength - building.length() - progress.length(); + + infoMain(building + ((pad > 0) ? chars(' ', pad) : "") + progress); + } + + // path to pom.xml + File currentPom = project.getFile(); + if (currentPom != null) { + MavenSession session = event.getSession(); + Path current = currentPom.toPath().toAbsolutePath().normalize(); + Path topDirectory = session.getTopDirectory(); + if (topDirectory != null && current.startsWith(topDirectory)) { + current = topDirectory.relativize(current); + } + logger.info(" from " + current); + } + + // ----------[ packaging ]---------- + prefix = chars('-', Math.max(0, (lineLength - project.getPackaging().length() - 4) / 2)); + suffix = chars('-', Math.max(0, lineLength - project.getPackaging().length() - 4 - prefix.length())); + infoMain(prefix + "[ " + project.getPackaging() + " ]" + suffix); + } + } + + @Override + public void mojoSkipped(ExecutionEvent event) { + if (logger.isWarnEnabled()) { + init(); + logger.warn( + "Goal '{}' requires online mode for execution but Maven is currently offline, skipping", + event.getMojoExecution().getGoal()); + } + } + + /** + *

--- mojo-artifactId:version:goal (mojo-executionId) @ project-artifactId ---
+ */ + @Override + public void mojoStarted(ExecutionEvent event) { + if (logger.isInfoEnabled()) { + init(); + logger.info(""); + + MessageBuilder buffer = builder().strong("--- "); + append(buffer, event.getMojoExecution()); + append(buffer, event.getProject()); + buffer.strong(" ---"); + + logger.info(buffer.toString()); + } + } + + // CHECKSTYLE_OFF: LineLength + /** + *
>>> mojo-artifactId:version:goal (mojo-executionId) > :forked-goal @ project-artifactId >>>
+ *
>>> mojo-artifactId:version:goal (mojo-executionId) > [lifecycle]phase @ project-artifactId >>>
+ */ + // CHECKSTYLE_ON: LineLength + @Override + public void forkStarted(ExecutionEvent event) { + if (logger.isInfoEnabled()) { + init(); + logger.info(""); + + MessageBuilder buffer = builder().strong(">>> "); + append(buffer, event.getMojoExecution()); + buffer.strong(" > "); + appendForkInfo(buffer, event.getMojoExecution().getMojoDescriptor()); + append(buffer, event.getProject()); + buffer.strong(" >>>"); + + logger.info(buffer.toString()); + } + } + + // CHECKSTYLE_OFF: LineLength + /** + *
<<< mojo-artifactId:version:goal (mojo-executionId) < :forked-goal @ project-artifactId <<<
+ *
<<< mojo-artifactId:version:goal (mojo-executionId) < [lifecycle]phase @ project-artifactId <<<
+ */ + // CHECKSTYLE_ON: LineLength + @Override + public void forkSucceeded(ExecutionEvent event) { + if (logger.isInfoEnabled()) { + init(); + logger.info(""); + + MessageBuilder buffer = builder().strong("<<< "); + append(buffer, event.getMojoExecution()); + buffer.strong(" < "); + appendForkInfo(buffer, event.getMojoExecution().getMojoDescriptor()); + append(buffer, event.getProject()); + buffer.strong(" <<<"); + + logger.info(buffer.toString()); + + logger.info(""); + } + } + + private void append(MessageBuilder buffer, MojoExecution me) { + String prefix = me.getMojoDescriptor().getPluginDescriptor().getGoalPrefix(); + if (prefix == null || prefix.isEmpty()) { + prefix = me.getGroupId() + ":" + me.getArtifactId(); + } + buffer.mojo(prefix + ':' + me.getVersion() + ':' + me.getGoal()); + if (me.getExecutionId() != null) { + buffer.a(' ').strong('(' + me.getExecutionId() + ')'); + } + } + + private void appendForkInfo(MessageBuilder buffer, MojoDescriptor md) { + StringBuilder buff = new StringBuilder(); + if (md.getExecutePhase() != null && !md.getExecutePhase().isEmpty()) { + // forked phase + if (md.getExecuteLifecycle() != null && !md.getExecuteLifecycle().isEmpty()) { + buff.append('['); + buff.append(md.getExecuteLifecycle()); + buff.append(']'); + } + buff.append(md.getExecutePhase()); + } else { + // forked goal + buff.append(':'); + buff.append(md.getExecuteGoal()); + } + buffer.strong(buff.toString()); + } + + private void append(MessageBuilder buffer, MavenProject project) { + buffer.a(" @ ").project(project.getArtifactId()); + } + + @Override + public void forkedProjectStarted(ExecutionEvent event) { + if (logger.isInfoEnabled() + && event.getMojoExecution().getForkedExecutions().size() > 1) { + init(); + logger.info(""); + infoLine('>'); + + infoMain("Forking " + event.getProject().getName() + " " + + event.getProject().getVersion()); + + infoLine('>'); + } + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/BootstrapCoreExtensionManager.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/BootstrapCoreExtensionManager.java new file mode 100644 index 0000000000..064c325ebc --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/BootstrapCoreExtensionManager.java @@ -0,0 +1,283 @@ +/* + * 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.maven.cling.extensions; + +import javax.inject.Inject; +import javax.inject.Named; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.apache.maven.RepositoryUtils; +import org.apache.maven.api.Service; +import org.apache.maven.api.Session; +import org.apache.maven.api.cli.extensions.CoreExtension; +import org.apache.maven.api.model.Plugin; +import org.apache.maven.api.services.ArtifactCoordinatesFactory; +import org.apache.maven.api.services.ArtifactManager; +import org.apache.maven.api.services.ArtifactResolver; +import org.apache.maven.api.services.Interpolator; +import org.apache.maven.api.services.InterpolatorException; +import org.apache.maven.api.services.RepositoryFactory; +import org.apache.maven.api.services.VersionParser; +import org.apache.maven.api.services.VersionRangeResolver; +import org.apache.maven.execution.DefaultMavenExecutionResult; +import org.apache.maven.execution.MavenExecutionRequest; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.extension.internal.CoreExports; +import org.apache.maven.extension.internal.CoreExtensionEntry; +import org.apache.maven.internal.impl.DefaultArtifactCoordinatesFactory; +import org.apache.maven.internal.impl.DefaultArtifactManager; +import org.apache.maven.internal.impl.DefaultArtifactResolver; +import org.apache.maven.internal.impl.DefaultModelVersionParser; +import org.apache.maven.internal.impl.DefaultRepositoryFactory; +import org.apache.maven.internal.impl.DefaultSession; +import org.apache.maven.internal.impl.DefaultVersionParser; +import org.apache.maven.internal.impl.DefaultVersionRangeResolver; +import org.apache.maven.internal.impl.InternalSession; +import org.apache.maven.internal.impl.model.DefaultInterpolator; +import org.apache.maven.plugin.PluginResolutionException; +import org.apache.maven.plugin.internal.DefaultPluginDependenciesResolver; +import org.apache.maven.resolver.MavenChainedWorkspaceReader; +import org.apache.maven.resolver.RepositorySystemSessionFactory; +import org.codehaus.plexus.DefaultPlexusContainer; +import org.codehaus.plexus.PlexusContainer; +import org.codehaus.plexus.classworlds.ClassWorld; +import org.codehaus.plexus.classworlds.realm.ClassRealm; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.RepositorySystemSession.CloseableSession; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.graph.DependencyFilter; +import org.eclipse.aether.internal.impl.DefaultChecksumPolicyProvider; +import org.eclipse.aether.internal.impl.DefaultRemoteRepositoryManager; +import org.eclipse.aether.internal.impl.DefaultUpdatePolicyAnalyzer; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.repository.WorkspaceReader; +import org.eclipse.aether.resolution.ArtifactResult; +import org.eclipse.aether.resolution.DependencyResult; +import org.eclipse.aether.util.filter.ExclusionsDependencyFilter; +import org.eclipse.aether.util.version.GenericVersionScheme; +import org.eclipse.sisu.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * BootstrapCoreExtensionManager + */ +@Named +public class BootstrapCoreExtensionManager { + public static final String STRATEGY_PARENT_FIRST = "parent-first"; + public static final String STRATEGY_PLUGIN = "plugin"; + public static final String STRATEGY_SELF_FIRST = "self-first"; + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private final DefaultPluginDependenciesResolver pluginDependenciesResolver; + + private final RepositorySystemSessionFactory repositorySystemSessionFactory; + + private final CoreExports coreExports; + + private final ClassWorld classWorld; + + private final ClassRealm parentRealm; + + private final WorkspaceReader ideWorkspaceReader; + + private final RepositorySystem repoSystem; + + @Inject + public BootstrapCoreExtensionManager( + DefaultPluginDependenciesResolver pluginDependenciesResolver, + RepositorySystemSessionFactory repositorySystemSessionFactory, + CoreExports coreExports, + PlexusContainer container, + @Nullable @Named("ide") WorkspaceReader ideWorkspaceReader, + RepositorySystem repoSystem) { + this.pluginDependenciesResolver = pluginDependenciesResolver; + this.repositorySystemSessionFactory = repositorySystemSessionFactory; + this.coreExports = coreExports; + this.classWorld = ((DefaultPlexusContainer) container).getClassWorld(); + this.parentRealm = container.getContainerRealm(); + this.ideWorkspaceReader = ideWorkspaceReader; + this.repoSystem = repoSystem; + } + + public List loadCoreExtensions( + MavenExecutionRequest request, Set providedArtifacts, List extensions) + throws Exception { + try (CloseableSession repoSession = repositorySystemSessionFactory + .newRepositorySessionBuilder(request) + .setWorkspaceReader(new MavenChainedWorkspaceReader(request.getWorkspaceReader(), ideWorkspaceReader)) + .build()) { + MavenSession mSession = new MavenSession(repoSession, request, new DefaultMavenExecutionResult()); + InternalSession iSession = new SimpleSession(mSession, repoSystem, null); + InternalSession.associate(repoSession, iSession); + + List repositories = RepositoryUtils.toRepos(request.getPluginArtifactRepositories()); + Function interpolator = createInterpolator(request); + + return resolveCoreExtensions(repoSession, repositories, providedArtifacts, extensions, interpolator); + } + } + + private List resolveCoreExtensions( + RepositorySystemSession repoSession, + List repositories, + Set providedArtifacts, + List configuration, + Function interpolator) + throws Exception { + List extensions = new ArrayList<>(); + + DependencyFilter dependencyFilter = new ExclusionsDependencyFilter(providedArtifacts); + + for (CoreExtension extension : configuration) { + List artifacts = + resolveExtension(extension, repoSession, repositories, dependencyFilter, interpolator); + if (!artifacts.isEmpty()) { + extensions.add(createExtension(extension, artifacts)); + } + } + + return Collections.unmodifiableList(extensions); + } + + private CoreExtensionEntry createExtension(CoreExtension extension, List artifacts) throws Exception { + String realmId = "coreExtension>" + extension.getGroupId() + ":" + extension.getArtifactId() + ":" + + extension.getVersion(); + final ClassRealm realm = classWorld.newRealm(realmId, null); + Set providedArtifacts = Collections.emptySet(); + String classLoadingStrategy = extension.getClassLoadingStrategy(); + if (STRATEGY_PARENT_FIRST.equals(classLoadingStrategy)) { + realm.importFrom(parentRealm, ""); + } else if (STRATEGY_PLUGIN.equals(classLoadingStrategy)) { + coreExports.getExportedPackages().forEach((p, cl) -> realm.importFrom(cl, p)); + providedArtifacts = coreExports.getExportedArtifacts(); + } else if (STRATEGY_SELF_FIRST.equals(classLoadingStrategy)) { + realm.setParentRealm(parentRealm); + } else { + throw new IllegalArgumentException("Unsupported class-loading strategy '" + + classLoadingStrategy + "'. Supported values are: " + STRATEGY_PARENT_FIRST + + ", " + STRATEGY_PLUGIN + " and " + STRATEGY_SELF_FIRST); + } + log.debug("Populating class realm {}", realm.getId()); + for (Artifact artifact : artifacts) { + String id = artifact.getGroupId() + ":" + artifact.getArtifactId(); + if (providedArtifacts.contains(id)) { + log.debug(" Excluded {}", id); + } else { + Path file = artifact.getPath(); + log.debug(" Included {} located at {}", id, file); + realm.addURL(file.toUri().toURL()); + } + } + return CoreExtensionEntry.discoverFrom( + realm, + Collections.singleton(artifacts.get(0).getPath().toFile()), + extension.getGroupId() + ":" + extension.getArtifactId(), + extension.getConfiguration()); + } + + private List resolveExtension( + CoreExtension extension, + RepositorySystemSession repoSession, + List repositories, + DependencyFilter dependencyFilter, + Function interpolator) + throws ExtensionResolutionException { + try { + /* TODO: Enhance the PluginDependenciesResolver to provide a + * resolveCoreExtension method which uses a CoreExtension + * object instead of a Plugin as this makes no sense. + */ + Plugin plugin = Plugin.newBuilder() + .groupId(interpolator.apply(extension.getGroupId())) + .artifactId(interpolator.apply(extension.getArtifactId())) + .version(interpolator.apply(extension.getVersion())) + .build(); + + DependencyResult result = pluginDependenciesResolver.resolveCoreExtension( + new org.apache.maven.model.Plugin(plugin), dependencyFilter, repositories, repoSession); + return result.getArtifactResults().stream() + .filter(ArtifactResult::isResolved) + .map(ArtifactResult::getArtifact) + .collect(Collectors.toList()); + } catch (PluginResolutionException | InterpolatorException e) { + throw new ExtensionResolutionException(extension, e); + } + } + + private static Function createInterpolator(MavenExecutionRequest request) { + Interpolator interpolator = new DefaultInterpolator(); + Function callback = v -> { + String r = request.getUserProperties().getProperty(v); + if (r == null) { + r = request.getSystemProperties().getProperty(v); + } + return r != null ? r : v; + }; + return v -> interpolator.interpolate(v, callback); + } + + static class SimpleSession extends DefaultSession { + SimpleSession( + MavenSession session, + RepositorySystem repositorySystem, + List repositories) { + super(session, repositorySystem, repositories, null, null, null); + } + + @Override + protected Session newSession( + MavenSession mavenSession, List repositories) { + return new SimpleSession(mavenSession, getRepositorySystem(), repositories); + } + + @Override + public T getService(Class clazz) throws NoSuchElementException { + if (clazz == ArtifactCoordinatesFactory.class) { + return (T) new DefaultArtifactCoordinatesFactory(); + } else if (clazz == VersionParser.class) { + return (T) new DefaultVersionParser(new DefaultModelVersionParser(new GenericVersionScheme())); + } else if (clazz == VersionRangeResolver.class) { + return (T) new DefaultVersionRangeResolver(repositorySystem); + } else if (clazz == ArtifactResolver.class) { + return (T) new DefaultArtifactResolver(); + } else if (clazz == ArtifactManager.class) { + return (T) new DefaultArtifactManager(this); + } else if (clazz == RepositoryFactory.class) { + return (T) new DefaultRepositoryFactory(new DefaultRemoteRepositoryManager( + new DefaultUpdatePolicyAnalyzer(), new DefaultChecksumPolicyProvider())); + } else if (clazz == Interpolator.class) { + return (T) new DefaultInterpolator(); + // } else if (clazz == ModelResolver.class) { + // return (T) new DefaultModelResolver(); + } + throw new NoSuchElementException("No service for " + clazz.getName()); + } + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/ExtensionConfigurationModule.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/ExtensionConfigurationModule.java new file mode 100644 index 0000000000..2b04b3abae --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/ExtensionConfigurationModule.java @@ -0,0 +1,75 @@ +/* + * 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.maven.cling.extensions; + +import java.util.function.Function; + +import com.google.inject.Binder; +import com.google.inject.Module; +import com.google.inject.name.Names; +import org.apache.maven.api.services.Interpolator; +import org.apache.maven.api.xml.XmlNode; +import org.apache.maven.extension.internal.CoreExtensionEntry; +import org.apache.maven.internal.impl.model.DefaultInterpolator; +import org.apache.maven.internal.xml.XmlNodeImpl; +import org.apache.maven.internal.xml.XmlPlexusConfiguration; +import org.apache.maven.model.v4.MavenTransformer; +import org.codehaus.plexus.configuration.PlexusConfiguration; + +public class ExtensionConfigurationModule implements Module { + + private final CoreExtensionEntry extension; + private final Function callback; + private final DefaultInterpolator interpolator = new DefaultInterpolator(); + + public ExtensionConfigurationModule(CoreExtensionEntry extension, Function callback) { + this.extension = extension; + this.callback = callback; + } + + @Override + public void configure(Binder binder) { + if (extension.getKey() != null) { + XmlNode configuration = extension.getConfiguration(); + if (configuration == null) { + configuration = new XmlNodeImpl("configuration"); + } + Function cb = Interpolator.memoize(callback); + Function it = s -> interpolator.interpolate(s, cb); + configuration = new ExtensionInterpolator(it).transform(configuration); + + binder.bind(XmlNode.class) + .annotatedWith(Names.named(extension.getKey())) + .toInstance(configuration); + binder.bind(PlexusConfiguration.class) + .annotatedWith(Names.named(extension.getKey())) + .toInstance(XmlPlexusConfiguration.toPlexusConfiguration(configuration)); + } + } + + static class ExtensionInterpolator extends MavenTransformer { + ExtensionInterpolator(Function transformer) { + super(transformer); + } + + public XmlNode transform(XmlNode node) { + return super.transform(node); + } + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/ExtensionResolutionException.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/ExtensionResolutionException.java new file mode 100644 index 0000000000..ad6cdf1d8e --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/ExtensionResolutionException.java @@ -0,0 +1,42 @@ +/* + * 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.maven.cling.extensions; + +import org.apache.maven.api.cli.extensions.CoreExtension; + +/** + * Exception occurring trying to resolve a plugin. + * + */ +public class ExtensionResolutionException extends Exception { + + private final CoreExtension extension; + + public ExtensionResolutionException(CoreExtension extension, Throwable cause) { + super( + "Extension " + extension.getId() + " or one of its dependencies could not be resolved: " + + cause.getMessage(), + cause); + this.extension = extension; + } + + public CoreExtension getExtension() { + return extension; + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java index cef034fe9a..4dc2550d11 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java @@ -42,9 +42,9 @@ import org.apache.maven.api.cli.Parser; import org.apache.maven.api.cli.ParserException; import org.apache.maven.api.cli.ParserRequest; import org.apache.maven.api.cli.extensions.CoreExtension; -import org.apache.maven.cli.CLIReportingUtils; -import org.apache.maven.cli.internal.extension.io.CoreExtensionsStaxReader; -import org.apache.maven.cli.props.MavenPropertiesLoader; +import org.apache.maven.cling.internal.extension.io.CoreExtensionsStaxReader; +import org.apache.maven.cling.props.MavenPropertiesLoader; +import org.apache.maven.cling.utils.CLIReportingUtils; import org.apache.maven.properties.internal.EnvironmentUtils; import org.apache.maven.properties.internal.SystemProperties; diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/CleanArgument.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/CleanArgument.java new file mode 100644 index 0000000000..f32e71c3e0 --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/CleanArgument.java @@ -0,0 +1,103 @@ +/* + * 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.maven.cling.invoker; + +import java.util.ArrayList; +import java.util.List; + +/** + * CleanArgument + */ +public class CleanArgument { + public static String[] cleanArgs(String[] args) { + try { + return doCleanArgs(args); + } catch (RuntimeException e) { + for (String a : args) { + System.out.println("Arg: '" + a + "'"); + } + throw e; + } + } + + private static String[] doCleanArgs(String[] args) { + List cleaned = new ArrayList<>(); + + StringBuilder currentArg = null; + + for (String arg : args) { + boolean addedToBuffer = false; + + if (arg.startsWith("\"")) { + // if we're in the process of building up another arg, push it and start over. + // this is for the case: "-Dfoo=bar "-Dfoo2=bar two" (note the first unterminated quote) + if (currentArg != null) { + cleaned.add(currentArg.toString()); + } + + // start building an argument here. + currentArg = new StringBuilder(arg.substring(1)); + addedToBuffer = true; + } + + // this has to be a separate "if" statement, to capture the case of: "-Dfoo=bar" + if (addedToBuffer && arg.endsWith("\"")) { + // if we're building an argument, keep doing so. + // if this is the case of "-Dfoo=bar", then we need to adjust the buffer. + if (!currentArg.isEmpty()) { + currentArg.setLength(currentArg.length() - 1); + } + + cleaned.add(currentArg.toString()); + + currentArg = null; + addedToBuffer = false; + continue; + } + + // if we haven't added this arg to the buffer, and we ARE building an argument + // buffer, then append it with a preceding space...again, not sure what else to + // do other than collapse whitespace. + // NOTE: The case of a trailing quote is handled by nullifying the arg buffer. + if (!addedToBuffer) { + if (currentArg != null) { + currentArg.append(' ').append(arg); + } else { + cleaned.add(arg); + } + } + } + + if (currentArg != null) { + cleaned.add(currentArg.toString()); + } + + int cleanedSz = cleaned.size(); + + String[] cleanArgs; + + if (cleanedSz == 0) { + cleanArgs = args; + } else { + cleanArgs = cleaned.toArray(new String[0]); + } + + return cleanArgs; + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java index 04ad3ebbd9..190b7d5862 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java @@ -34,7 +34,6 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.ParseException; import org.apache.maven.api.cli.Options; import org.apache.maven.api.cli.ParserRequest; -import org.apache.maven.cli.CleanArgument; import org.apache.maven.jline.MessageUtils; import static java.util.Objects.requireNonNull; diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java index e645ca9989..dc6ca2a698 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java @@ -62,14 +62,14 @@ import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy; import org.apache.maven.artifact.repository.MavenArtifactRepository; import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout; import org.apache.maven.bridge.MavenRepositorySystem; -import org.apache.maven.cli.CLIReportingUtils; -import org.apache.maven.cli.logging.Slf4jConfiguration; -import org.apache.maven.cli.logging.Slf4jConfigurationFactory; -import org.apache.maven.cli.transfer.ConsoleMavenTransferListener; -import org.apache.maven.cli.transfer.QuietMavenTransferListener; -import org.apache.maven.cli.transfer.SimplexTransferListener; -import org.apache.maven.cli.transfer.Slf4jMavenTransferListener; import org.apache.maven.cling.invoker.mvn.ProtoSession; +import org.apache.maven.cling.logging.Slf4jConfiguration; +import org.apache.maven.cling.logging.Slf4jConfigurationFactory; +import org.apache.maven.cling.transfer.ConsoleMavenTransferListener; +import org.apache.maven.cling.transfer.QuietMavenTransferListener; +import org.apache.maven.cling.transfer.SimplexTransferListener; +import org.apache.maven.cling.transfer.Slf4jMavenTransferListener; +import org.apache.maven.cling.utils.CLIReportingUtils; import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.internal.impl.SettingsUtilsV4; import org.apache.maven.jline.FastTerminal; diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/PlexusContainerCapsuleFactory.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/PlexusContainerCapsuleFactory.java index 54bbe0c9e5..3e70d4c971 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/PlexusContainerCapsuleFactory.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/PlexusContainerCapsuleFactory.java @@ -37,9 +37,9 @@ import org.apache.maven.api.cli.Options; import org.apache.maven.api.cli.extensions.CoreExtension; import org.apache.maven.api.services.MessageBuilderFactory; import org.apache.maven.api.services.SettingsBuilder; -import org.apache.maven.cli.ExtensionConfigurationModule; -import org.apache.maven.cli.internal.BootstrapCoreExtensionManager; -import org.apache.maven.cli.logging.Slf4jLoggerManager; +import org.apache.maven.cling.extensions.BootstrapCoreExtensionManager; +import org.apache.maven.cling.extensions.ExtensionConfigurationModule; +import org.apache.maven.cling.logging.Slf4jLoggerManager; import org.apache.maven.di.Injector; import org.apache.maven.execution.DefaultMavenExecutionRequest; import org.apache.maven.execution.MavenExecutionRequest; diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/Utils.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/Utils.java index 2cb01e2c19..b278724a77 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/Utils.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/Utils.java @@ -32,7 +32,7 @@ import java.util.function.Function; import org.apache.maven.api.annotations.Nonnull; import org.apache.maven.api.annotations.Nullable; import org.apache.maven.api.services.model.RootLocator; -import org.apache.maven.cli.logging.Slf4jConfiguration; +import org.apache.maven.cling.logging.Slf4jConfiguration; import org.apache.maven.execution.MavenExecutionRequest; import org.codehaus.plexus.interpolation.AbstractValueSource; import org.codehaus.plexus.interpolation.BasicInterpolator; diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenInvoker.java index d08056d3e7..665256d127 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenInvoker.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenInvoker.java @@ -46,11 +46,11 @@ import org.apache.maven.api.services.ToolchainsBuilder; import org.apache.maven.api.services.ToolchainsBuilderRequest; import org.apache.maven.api.services.ToolchainsBuilderResult; import org.apache.maven.api.services.model.ModelProcessor; -import org.apache.maven.cli.CLIReportingUtils; -import org.apache.maven.cli.event.ExecutionEventLogger; +import org.apache.maven.cling.event.ExecutionEventLogger; import org.apache.maven.cling.invoker.LookupInvoker; import org.apache.maven.cling.invoker.ProtoLookup; import org.apache.maven.cling.invoker.Utils; +import org.apache.maven.cling.utils.CLIReportingUtils; import org.apache.maven.eventspy.internal.EventSpyDispatcher; import org.apache.maven.exception.DefaultExceptionHandler; import org.apache.maven.exception.ExceptionHandler; diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/DefaultEncryptInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/DefaultEncryptInvoker.java index 65583ee217..cbe3cd3ecd 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/DefaultEncryptInvoker.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/DefaultEncryptInvoker.java @@ -26,9 +26,9 @@ import java.util.Map; import org.apache.maven.api.cli.mvnenc.EncryptInvoker; import org.apache.maven.api.cli.mvnenc.EncryptInvokerRequest; import org.apache.maven.api.cli.mvnenc.EncryptOptions; -import org.apache.maven.cli.CLIReportingUtils; import org.apache.maven.cling.invoker.LookupInvoker; import org.apache.maven.cling.invoker.ProtoLookup; +import org.apache.maven.cling.utils.CLIReportingUtils; import org.jline.consoleui.prompt.ConsolePrompt; import org.jline.reader.LineReader; import org.jline.reader.LineReaderBuilder; diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/BaseSlf4jConfiguration.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/logging/BaseSlf4jConfiguration.java similarity index 95% rename from compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/BaseSlf4jConfiguration.java rename to impl/maven-cli/src/main/java/org/apache/maven/cling/logging/BaseSlf4jConfiguration.java index a4b1fd8d8d..29d6300457 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/BaseSlf4jConfiguration.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/logging/BaseSlf4jConfiguration.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.maven.cli.logging; +package org.apache.maven.cling.logging; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,10 +29,12 @@ import org.slf4j.LoggerFactory; public class BaseSlf4jConfiguration implements Slf4jConfiguration { private static final Logger LOGGER = LoggerFactory.getLogger(BaseSlf4jConfiguration.class); + @Override public void setRootLoggerLevel(Level level) { LOGGER.warn("setRootLoggerLevel: operation not supported"); } + @Override public void activate() { LOGGER.warn("activate(): operation not supported"); } diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/Slf4jConfiguration.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/logging/Slf4jConfiguration.java similarity index 97% rename from compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/Slf4jConfiguration.java rename to impl/maven-cli/src/main/java/org/apache/maven/cling/logging/Slf4jConfiguration.java index 7a162fa996..28fa676f54 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/Slf4jConfiguration.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/logging/Slf4jConfiguration.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.maven.cli.logging; +package org.apache.maven.cling.logging; /** * Interface for configuration operations on loggers, which are not available in slf4j, then require per-slf4f-binding diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/Slf4jConfigurationFactory.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/logging/Slf4jConfigurationFactory.java similarity index 80% rename from compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/Slf4jConfigurationFactory.java rename to impl/maven-cli/src/main/java/org/apache/maven/cling/logging/Slf4jConfigurationFactory.java index 2d65fe8ae8..f2a560ac94 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/Slf4jConfigurationFactory.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/logging/Slf4jConfigurationFactory.java @@ -16,15 +16,16 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.maven.cli.logging; +package org.apache.maven.cling.logging; import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.util.Enumeration; import java.util.Properties; -import org.apache.maven.cli.logging.impl.UnsupportedSlf4jBindingConfiguration; +import org.apache.maven.cling.logging.impl.UnsupportedSlf4jBindingConfiguration; import org.slf4j.ILoggerFactory; /** @@ -56,9 +57,15 @@ public class Slf4jConfigurationFactory { } String impl = properties.getProperty(slf4jBinding); if (impl != null) { - return (Slf4jConfiguration) Class.forName(impl).newInstance(); + return (Slf4jConfiguration) + Class.forName(impl).getDeclaredConstructor().newInstance(); } - } catch (IOException | ClassNotFoundException | IllegalAccessException | InstantiationException ex) { + } catch (IOException + | ClassNotFoundException + | NoSuchMethodException + | InvocationTargetException + | IllegalAccessException + | InstantiationException ex) { // ignore and move on to the next } } diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/Slf4jLogger.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/logging/Slf4jLogger.java similarity index 90% rename from compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/Slf4jLogger.java rename to impl/maven-cli/src/main/java/org/apache/maven/cling/logging/Slf4jLogger.java index 0dbce41f12..5332b9ca92 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/Slf4jLogger.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/logging/Slf4jLogger.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.maven.cli.logging; +package org.apache.maven.cling.logging; import org.apache.maven.logging.ProjectBuildLogAppender; import org.codehaus.plexus.logging.Logger; @@ -29,80 +29,95 @@ import org.codehaus.plexus.logging.Logger; */ public class Slf4jLogger implements Logger { - private org.slf4j.Logger logger; - private String projectId; + private final org.slf4j.Logger logger; + private final String projectId; public Slf4jLogger(org.slf4j.Logger logger) { this.logger = logger; this.projectId = ProjectBuildLogAppender.getProjectId(); } + @Override public void debug(String message) { setMdc(); logger.debug(message); } + @Override public void debug(String message, Throwable throwable) { setMdc(); logger.debug(message, throwable); } + @Override public boolean isDebugEnabled() { return logger.isDebugEnabled(); } + @Override public void info(String message) { setMdc(); logger.info(message); } + @Override public void info(String message, Throwable throwable) { setMdc(); logger.info(message, throwable); } + @Override public boolean isInfoEnabled() { return logger.isInfoEnabled(); } + @Override public void warn(String message) { setMdc(); logger.warn(message); } + @Override public void warn(String message, Throwable throwable) { setMdc(); logger.warn(message, throwable); } + @Override public boolean isWarnEnabled() { return logger.isWarnEnabled(); } + @Override public void error(String message) { setMdc(); logger.error(message); } + @Override public void error(String message, Throwable throwable) { setMdc(); logger.error(message, throwable); } + @Override public boolean isErrorEnabled() { return logger.isErrorEnabled(); } + @Override public void fatalError(String message) { setMdc(); logger.error(message); } + @Override public void fatalError(String message, Throwable throwable) { setMdc(); logger.error(message, throwable); } + @Override public boolean isFatalErrorEnabled() { return logger.isErrorEnabled(); } @@ -110,6 +125,7 @@ public class Slf4jLogger implements Logger { /** * Warning: ignored (always return 0 == Logger.LEVEL_DEBUG). */ + @Override public int getThreshold() { return 0; } @@ -117,15 +133,18 @@ public class Slf4jLogger implements Logger { /** * Warning: ignored. */ + @Override public void setThreshold(int threshold) {} /** * Warning: ignored (always return null). */ + @Override public Logger getChildLogger(String name) { return null; } + @Override public String getName() { return logger.getName(); } diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/Slf4jLoggerManager.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/logging/Slf4jLoggerManager.java similarity index 90% rename from compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/Slf4jLoggerManager.java rename to impl/maven-cli/src/main/java/org/apache/maven/cling/logging/Slf4jLoggerManager.java index 4342390596..3134dd8d08 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/Slf4jLoggerManager.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/logging/Slf4jLoggerManager.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.maven.cli.logging; +package org.apache.maven.cling.logging; import org.codehaus.plexus.logging.Logger; import org.codehaus.plexus.logging.LoggerManager; @@ -24,20 +24,21 @@ import org.slf4j.ILoggerFactory; import org.slf4j.LoggerFactory; /** - * Use an SLF4J {@link org.slf4j.ILoggerFactory} as a backing for a Plexus - * {@link org.codehaus.plexus.logging.LoggerManager}, + * Use an SLF4J {@link ILoggerFactory} as a backing for a Plexus + * {@link LoggerManager}, * ignoring Plexus logger API parts that are not classical and probably not really used. * * @since 3.1 */ public class Slf4jLoggerManager implements LoggerManager { - private ILoggerFactory loggerFactory; + private final ILoggerFactory loggerFactory; public Slf4jLoggerManager() { loggerFactory = LoggerFactory.getILoggerFactory(); } + @Override public Logger getLoggerForComponent(String role) { return new Slf4jLogger(loggerFactory.getLogger(role)); } @@ -47,6 +48,7 @@ public class Slf4jLoggerManager implements LoggerManager { * Warning: this does not conform to logger name as class name convention. * (and what about null and default hint equivalence?) */ + @Override public Logger getLoggerForComponent(String role, String hint) { return (null == hint ? getLoggerForComponent(role) @@ -60,16 +62,19 @@ public class Slf4jLoggerManager implements LoggerManager { /** * Warning: ignored. */ + @Override public void returnComponentLogger(String role) {} /** * Warning: ignored. */ + @Override public void returnComponentLogger(String role, String hint) {} /** * Warning: ignored (always return 0). */ + @Override public int getThreshold() { return 0; } @@ -77,16 +82,19 @@ public class Slf4jLoggerManager implements LoggerManager { /** * Warning: ignored. */ + @Override public void setThreshold(int threshold) {} /** * Warning: ignored. */ + @Override public void setThresholds(int threshold) {} /** * Warning: ignored (always return 0). */ + @Override public int getActiveLoggerCount() { return 0; } diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/impl/Log4j2Configuration.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/logging/impl/Log4j2Configuration.java similarity index 93% rename from compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/impl/Log4j2Configuration.java rename to impl/maven-cli/src/main/java/org/apache/maven/cling/logging/impl/Log4j2Configuration.java index d6171f776f..1f02e57750 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/impl/Log4j2Configuration.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/logging/impl/Log4j2Configuration.java @@ -16,9 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.maven.cli.logging.impl; +package org.apache.maven.cling.logging.impl; -import org.apache.maven.cli.logging.BaseSlf4jConfiguration; +import org.apache.maven.cling.logging.BaseSlf4jConfiguration; /** * Configuration for slf4j-log4j2. diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/impl/LogbackConfiguration.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/logging/impl/LogbackConfiguration.java similarity index 93% rename from compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/impl/LogbackConfiguration.java rename to impl/maven-cli/src/main/java/org/apache/maven/cling/logging/impl/LogbackConfiguration.java index e2f0cb60fc..4481486218 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/impl/LogbackConfiguration.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/logging/impl/LogbackConfiguration.java @@ -16,9 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.maven.cli.logging.impl; +package org.apache.maven.cling.logging.impl; -import org.apache.maven.cli.logging.BaseSlf4jConfiguration; +import org.apache.maven.cling.logging.BaseSlf4jConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/impl/MavenSimpleConfiguration.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/logging/impl/MavenSimpleConfiguration.java similarity index 94% rename from compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/impl/MavenSimpleConfiguration.java rename to impl/maven-cli/src/main/java/org/apache/maven/cling/logging/impl/MavenSimpleConfiguration.java index aa73239755..5147cd8864 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/impl/MavenSimpleConfiguration.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/logging/impl/MavenSimpleConfiguration.java @@ -16,9 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.maven.cli.logging.impl; +package org.apache.maven.cling.logging.impl; -import org.apache.maven.cli.logging.BaseSlf4jConfiguration; +import org.apache.maven.cling.logging.BaseSlf4jConfiguration; import org.apache.maven.slf4j.MavenLoggerFactory; import org.slf4j.ILoggerFactory; import org.slf4j.LoggerFactory; diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/impl/UnsupportedSlf4jBindingConfiguration.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/logging/impl/UnsupportedSlf4jBindingConfiguration.java similarity index 92% rename from compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/impl/UnsupportedSlf4jBindingConfiguration.java rename to impl/maven-cli/src/main/java/org/apache/maven/cling/logging/impl/UnsupportedSlf4jBindingConfiguration.java index 8c29235724..ce4d4f9d7a 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/logging/impl/UnsupportedSlf4jBindingConfiguration.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/logging/impl/UnsupportedSlf4jBindingConfiguration.java @@ -16,13 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.maven.cli.logging.impl; +package org.apache.maven.cling.logging.impl; import java.net.URL; import java.util.Map; import java.util.Set; -import org.apache.maven.cli.logging.BaseSlf4jConfiguration; +import org.apache.maven.cling.logging.BaseSlf4jConfiguration; /** * Pseudo-configuration for unsupported SLF4J binding. diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/props/MavenProperties.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/props/MavenProperties.java new file mode 100644 index 0000000000..5817bab379 --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/props/MavenProperties.java @@ -0,0 +1,1153 @@ +/* + * 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.maven.cling.props; + +import java.io.FilterWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +import org.apache.maven.internal.impl.model.DefaultInterpolator; + +/** + * Enhancement of the standard Properties + * managing the maintain of comments, etc. + */ +public class MavenProperties extends AbstractMap { + + /** Constant for the supported comment characters.*/ + private static final String COMMENT_CHARS = "#!"; + + /** The list of possible key/value separators */ + private static final char[] SEPARATORS = new char[] {'=', ':'}; + + /** The white space characters used as key/value separators. */ + private static final char[] WHITE_SPACE = new char[] {' ', '\t', '\f'}; + + /** + * Unless standard java props, use UTF-8 + */ + static final String DEFAULT_ENCODING = StandardCharsets.UTF_8.name(); + + /** Constant for the platform specific line separator.*/ + private static final String LINE_SEPARATOR = System.lineSeparator(); + + /** Constant for the radix of hex numbers.*/ + private static final int HEX_RADIX = 16; + + /** Constant for the length of a unicode literal.*/ + private static final int UNICODE_LEN = 4; + + private final Map storage = new LinkedHashMap<>(); + private final Map layout = new LinkedHashMap<>(); + private List header; + private List footer; + private Path location; + private Function callback; + boolean substitute = true; + boolean typed; + + public MavenProperties() {} + + public MavenProperties(Path location) throws IOException { + this(location, null); + } + + public MavenProperties(Path location, Function callback) throws IOException { + this.location = location; + this.callback = callback; + if (Files.exists(location)) { + load(location); + } + } + + public MavenProperties(boolean substitute) { + this.substitute = substitute; + } + + public MavenProperties(Path location, boolean substitute) { + this.location = location; + this.substitute = substitute; + } + + public void load(Path location) throws IOException { + try (InputStream is = Files.newInputStream(location)) { + load(is); + } + } + + public void load(URL location) throws IOException { + try (InputStream is = location.openStream()) { + load(is); + } + } + + public void load(InputStream is) throws IOException { + load(new InputStreamReader(is, DEFAULT_ENCODING)); + } + + public void load(Reader reader) throws IOException { + loadLayout(reader, false); + } + + public void save() throws IOException { + save(this.location); + } + + public void save(Path location) throws IOException { + try (OutputStream os = Files.newOutputStream(location)) { + save(os); + } + } + + public void save(OutputStream os) throws IOException { + save(new OutputStreamWriter(os, DEFAULT_ENCODING)); + } + + public void save(Writer writer) throws IOException { + saveLayout(writer, typed); + } + + /** + * Store a properties into a output stream, preserving comments, special character, etc. + * This method is mainly to be compatible with the java.util.Properties class. + * + * @param os an output stream. + * @param comment this parameter is ignored as this Properties + * @throws IOException If storing fails + */ + public void store(OutputStream os, String comment) throws IOException { + this.save(os); + } + + /** + * Searches for the property with the specified key in this property list. + * + * @param key the property key. + * @return the value in this property list with the specified key value. + */ + public String getProperty(String key) { + return this.get(key); + } + + /** + * Searches for the property with the specified key in this property list. If the key is not found in this property + * list, the default property list, and its defaults, recursively, are then checked. The method returns the default + * value argument if the property is not found. + * + * @param key the property key. + * @param defaultValue a default value. + * @return The property value of the default value + */ + public String getProperty(String key, String defaultValue) { + if (this.get(key) != null) { + return this.get(key); + } + return defaultValue; + } + + @Override + public Set> entrySet() { + return new AbstractSet<>() { + @Override + public Iterator> iterator() { + return new Iterator<>() { + final Iterator> keyIterator = + storage.entrySet().iterator(); + + public boolean hasNext() { + return keyIterator.hasNext(); + } + + public Entry next() { + final Entry entry = keyIterator.next(); + return new Entry() { + public String getKey() { + return entry.getKey(); + } + + public String getValue() { + return entry.getValue(); + } + + public String setValue(String value) { + String old = entry.setValue(value); + if (old == null || !old.equals(value)) { + Layout l = layout.get(entry.getKey()); + if (l != null) { + l.clearValue(); + } + } + return old; + } + }; + } + + public void remove() { + keyIterator.remove(); + } + }; + } + + @Override + public int size() { + return storage.size(); + } + }; + } + + /** + * Returns an enumeration of all the keys in this property list, including distinct keys in the default property + * list if a key of the same name has not already been found from the main properties list. + * + * @return an enumeration of all the keys in this property list, including the keys in the default property list. + */ + public Enumeration propertyNames() { + return Collections.enumeration(storage.keySet()); + } + + /** + * Calls the map method put. Provided for parallelism with the getProperty method. + * Enforces use of strings for property keys and values. The value returned is the result of the map call to put. + * + * @param key the key to be placed into this property list. + * @param value the value corresponding to the key. + * @return the previous value of the specified key in this property list, or null if it did not have one. + */ + public Object setProperty(String key, String value) { + return this.put(key, value); + } + + @Override + public String put(String key, String value) { + String old = storage.put(key, value); + if (old == null || !old.equals(value)) { + Layout l = layout.get(key); + if (l != null) { + l.clearValue(); + } + } + return old; + } + + void putAllSubstituted(Map m) { + storage.putAll(m); + } + + public String put(String key, List commentLines, List valueLines) { + commentLines = new ArrayList<>(commentLines); + valueLines = new ArrayList<>(valueLines); + String escapedKey = escapeKey(key); + StringBuilder sb = new StringBuilder(); + // int lastLine = valueLines.size() - 1; + if (valueLines.isEmpty()) { + valueLines.add(escapedKey + "="); + sb.append(escapedKey).append("="); + } else { + String val0 = valueLines.get(0); + String rv0 = typed ? val0 : escapeJava(val0); + if (!val0.trim().startsWith(escapedKey)) { + valueLines.set(0, escapedKey + " = " + rv0 /*+ (0 < lastLine? "\\": "")*/); + sb.append(escapedKey).append(" = ").append(rv0); + } else { + valueLines.set(0, rv0 /*+ (0 < lastLine? "\\": "")*/); + sb.append(rv0); + } + } + for (int i = 1; i < valueLines.size(); i++) { + String val = valueLines.get(i); + valueLines.set(i, typed ? val : escapeJava(val) /*+ (i < lastLine? "\\": "")*/); + while (!val.isEmpty() && Character.isWhitespace(val.charAt(0))) { + val = val.substring(1); + } + sb.append(val); + } + String[] property = PropertiesReader.parseProperty(sb.toString()); + this.layout.put(key, new Layout(commentLines, valueLines)); + return storage.put(key, property[1]); + } + + public String put(String key, List commentLines, String value) { + commentLines = new ArrayList<>(commentLines); + this.layout.put(key, new Layout(commentLines, null)); + return storage.put(key, value); + } + + public String put(String key, String comment, String value) { + return put(key, Collections.singletonList(comment), value); + } + + public boolean update(Map props) { + MavenProperties properties; + if (props instanceof MavenProperties) { + properties = (MavenProperties) props; + } else { + properties = new MavenProperties(); + properties.putAll(props); + } + return update(properties); + } + + public boolean update(MavenProperties properties) { + boolean modified = false; + // Remove "removed" properties from the cfg file + for (String key : new ArrayList(this.keySet())) { + if (!properties.containsKey(key)) { + this.remove(key); + modified = true; + } + } + // Update existing keys + for (String key : properties.keySet()) { + String v = this.get(key); + List comments = properties.getComments(key); + List value = properties.getRaw(key); + if (v == null) { + this.put(key, comments, value); + modified = true; + } else if (!v.equals(properties.get(key))) { + if (comments.isEmpty()) { + comments = this.getComments(key); + } + this.put(key, comments, value); + modified = true; + } + } + return modified; + } + + public List getRaw(String key) { + if (layout.containsKey(key)) { + if (layout.get(key).getValueLines() != null) { + return new ArrayList(layout.get(key).getValueLines()); + } + } + List result = new ArrayList(); + if (storage.containsKey(key)) { + result.add(storage.get(key)); + } + return result; + } + + public List getComments(String key) { + if (layout.containsKey(key)) { + if (layout.get(key).getCommentLines() != null) { + return new ArrayList(layout.get(key).getCommentLines()); + } + } + return new ArrayList(); + } + + @Override + public String remove(Object key) { + Layout l = layout.get(key); + if (l != null) { + l.clearValue(); + } + return storage.remove(key); + } + + @Override + public void clear() { + for (Layout l : layout.values()) { + l.clearValue(); + } + storage.clear(); + } + + /** + * Return the comment header. + * + * @return the comment header + */ + public List getHeader() { + return header; + } + + /** + * Set the comment header. + * + * @param header the header to use + */ + public void setHeader(List header) { + this.header = header; + } + + /** + * Return the comment footer. + * + * @return the comment footer + */ + public List getFooter() { + return footer; + } + + /** + * Set the comment footer. + * + * @param footer the footer to use + */ + public void setFooter(List footer) { + this.footer = footer; + } + + /** + * Reads a properties file and stores its internal structure. The found + * properties will be added to the associated configuration object. + * + * @param in the reader to the properties file + * @throws IOException if an error occurs + */ + protected void loadLayout(Reader in, boolean maybeTyped) throws IOException { + PropertiesReader reader = new PropertiesReader(in, maybeTyped); + boolean hasProperty = false; + while (reader.nextProperty()) { + hasProperty = true; + storage.put(reader.getPropertyName(), reader.getPropertyValue()); + int idx = checkHeaderComment(reader.getCommentLines()); + layout.put( + reader.getPropertyName(), + new Layout( + idx < reader.getCommentLines().size() + ? new ArrayList<>(reader.getCommentLines() + .subList( + idx, + reader.getCommentLines().size())) + : null, + new ArrayList<>(reader.getValueLines()))); + } + typed = maybeTyped && reader.typed != null && reader.typed; + if (!typed) { + for (Entry e : storage.entrySet()) { + e.setValue(unescapeJava(e.getValue())); + } + } + if (hasProperty) { + footer = new ArrayList<>(reader.getCommentLines()); + } else { + header = new ArrayList<>(reader.getCommentLines()); + } + if (substitute) { + substitute(); + } + } + + public void substitute() { + substitute(callback); + } + + public void substitute(Function callback) { + new DefaultInterpolator().interpolate(storage, callback); + } + + /** + * Writes the properties file to the given writer, preserving as much of its + * structure as possible. + * + * @param out the writer + * @throws IOException if an error occurs + */ + protected void saveLayout(Writer out, boolean typed) throws IOException { + PropertiesWriter writer = new PropertiesWriter(out, typed); + if (header != null) { + for (String s : header) { + writer.writeln(s); + } + } + + for (String key : storage.keySet()) { + Layout l = layout.get(key); + if (l != null && l.getCommentLines() != null) { + for (String s : l.getCommentLines()) { + writer.writeln(s); + } + } + if (l != null && l.getValueLines() != null) { + for (int i = 0; i < l.getValueLines().size(); i++) { + String s = l.getValueLines().get(i); + if (i < l.getValueLines().size() - 1) { + writer.writeln(s + "\\"); + } else { + writer.writeln(s); + } + } + } else { + writer.writeProperty(key, storage.get(key)); + } + } + if (footer != null) { + for (String s : footer) { + writer.writeln(s); + } + } + writer.flush(); + } + + /** + * Checks if parts of the passed in comment can be used as header comment. + * This method checks whether a header comment can be defined (i.e. whether + * this is the first comment in the loaded file). If this is the case, it is + * searched for the lates blank line. This line will mark the end of the + * header comment. The return value is the index of the first line in the + * passed in list, which does not belong to the header comment. + * + * @param commentLines the comment lines + * @return the index of the next line after the header comment + */ + private int checkHeaderComment(List commentLines) { + if (getHeader() == null && layout.isEmpty()) { + // This is the first comment. Search for blank lines. + int index = commentLines.size() - 1; + while (index >= 0 && !commentLines.get(index).isEmpty()) { + index--; + } + setHeader(new ArrayList(commentLines.subList(0, index + 1))); + return index + 1; + } else { + return 0; + } + } + + /** + * Tests whether a line is a comment, i.e. whether it starts with a comment + * character. + * + * @param line the line + * @return a flag if this is a comment line + */ + static boolean isCommentLine(String line) { + String s = line.trim(); + // blank lines are also treated as comment lines + return s.isEmpty() || COMMENT_CHARS.indexOf(s.charAt(0)) >= 0; + } + + /** + *

Unescapes any Java literals found in the String to a + * Writer.

This is a slightly modified version of the + * StringEscapeUtils.unescapeJava() function in commons-lang that doesn't + * drop escaped separators (i.e '\,'). + * + * @param str the String to unescape, may be null + * @return the processed string + * @throws IllegalArgumentException if the Writer is null + */ + protected static String unescapeJava(String str) { + if (str == null) { + return null; + } + int sz = str.length(); + StringBuilder out = new StringBuilder(sz); + StringBuilder unicode = new StringBuilder(UNICODE_LEN); + boolean hadSlash = false; + boolean inUnicode = false; + for (int i = 0; i < sz; i++) { + char ch = str.charAt(i); + if (inUnicode) { + // if in unicode, then we're reading unicode + // values in somehow + unicode.append(ch); + if (unicode.length() == UNICODE_LEN) { + // unicode now contains the four hex digits + // which represents our unicode character + try { + int value = Integer.parseInt(unicode.toString(), HEX_RADIX); + out.append((char) value); + unicode.setLength(0); + inUnicode = false; + hadSlash = false; + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("Unable to parse unicode value: " + unicode, nfe); + } + } + continue; + } + + if (hadSlash) { + // handle an escaped value + hadSlash = false; + switch (ch) { + case '\\': + out.append('\\'); + break; + case '\'': + out.append('\''); + break; + case '\"': + out.append('"'); + break; + case 'r': + out.append('\r'); + break; + case 'f': + out.append('\f'); + break; + case 't': + out.append('\t'); + break; + case 'n': + out.append('\n'); + break; + case 'b': + out.append('\b'); + break; + case 'u': + // uh-oh, we're in unicode country.... + inUnicode = true; + break; + default: + out.append(ch); + break; + } + continue; + } else if (ch == '\\') { + hadSlash = true; + continue; + } + out.append(ch); + } + + if (hadSlash) { + // then we're in the weird case of a \ at the end of the + // string, let's output it anyway. + out.append('\\'); + } + + return out.toString(); + } + + /** + *

Escapes the characters in a String using Java String rules.

+ * + *

Deals correctly with quotes and control-chars (tab, backslash, cr, ff, etc.)

+ * + *

So a tab becomes the characters '\\' and + * 't'.

+ * + *

The only difference between Java strings and JavaScript strings + * is that in JavaScript, a single quote must be escaped.

+ * + *

Example:

+ *
+     * input string: He didn't say, "Stop!"
+     * output string: He didn't say, \"Stop!\"
+     * 
+ * + * + * @param str String to escape values in, may be null + * @return String with escaped values, null if null string input + */ + @SuppressWarnings("checkstyle:MagicNumber") + protected static String escapeJava(String str) { + if (str == null) { + return null; + } + int sz = str.length(); + StringBuilder out = new StringBuilder(sz * 2); + for (int i = 0; i < sz; i++) { + char ch = str.charAt(i); + // handle unicode + if (ch > 0xfff) { + out.append("\\u").append(hex(ch)); + } else if (ch > 0xff) { + out.append("\\u0").append(hex(ch)); + } else if (ch > 0x7f) { + out.append("\\u00").append(hex(ch)); + } else if (ch < 32) { + switch (ch) { + case '\b': + out.append('\\'); + out.append('b'); + break; + case '\n': + out.append('\\'); + out.append('n'); + break; + case '\t': + out.append('\\'); + out.append('t'); + break; + case '\f': + out.append('\\'); + out.append('f'); + break; + case '\r': + out.append('\\'); + out.append('r'); + break; + default: + if (ch > 0xf) { + out.append("\\u00").append(hex(ch)); + } else { + out.append("\\u000").append(hex(ch)); + } + break; + } + } else { + switch (ch) { + case '"': + out.append('\\'); + out.append('"'); + break; + case '\\': + out.append('\\'); + out.append('\\'); + break; + default: + out.append(ch); + break; + } + } + } + return out.toString(); + } + + /** + *

Returns an upper case hexadecimal String for the given + * character.

+ * + * @param ch The character to convert. + * @return An upper case hexadecimal String + */ + protected static String hex(char ch) { + return Integer.toHexString(ch).toUpperCase(Locale.ENGLISH); + } + + /** + *

Checks if the value is in the given array.

+ * + *

The method returns false if a null array is passed in.

+ * + * @param array the array to search through + * @param valueToFind the value to find + * @return true if the array contains the object + */ + public static boolean contains(char[] array, char valueToFind) { + if (array == null) { + return false; + } + for (char c : array) { + if (valueToFind == c) { + return true; + } + } + return false; + } + + /** + * Escape the separators in the key. + * + * @param key the key + * @return the escaped key + */ + private static String escapeKey(String key) { + StringBuilder newkey = new StringBuilder(); + + for (int i = 0; i < key.length(); i++) { + char c = key.charAt(i); + + if (contains(SEPARATORS, c) || contains(WHITE_SPACE, c)) { + // escape the separator + newkey.append('\\'); + newkey.append(c); + } else { + newkey.append(c); + } + } + + return newkey.toString(); + } + + /** + * This class is used to read properties lines. These lines do + * not terminate with new-line chars but rather when there is no + * backslash sign a the end of the line. This is used to + * concatenate multiple lines for readability. + */ + public static class PropertiesReader extends LineNumberReader { + /** Stores the comment lines for the currently processed property.*/ + private final List commentLines; + + /** Stores the value lines for the currently processed property.*/ + private final List valueLines; + + /** Stores the name of the last read property.*/ + private String propertyName; + + /** Stores the value of the last read property.*/ + private String propertyValue; + + private boolean maybeTyped; + + /** Stores if the properties are typed or not */ + Boolean typed; + + /** + * Creates a new instance of PropertiesReader and sets + * the underlaying reader and the list delimiter. + * + * @param reader the reader + */ + public PropertiesReader(Reader reader, boolean maybeTyped) { + super(reader); + commentLines = new ArrayList<>(); + valueLines = new ArrayList<>(); + this.maybeTyped = maybeTyped; + } + + /** + * Reads a property line. Returns null if Stream is + * at EOF. Concatenates lines ending with "\". + * Skips lines beginning with "#" or "!" and empty lines. + * The return value is a property definition (<name> + * = <value>) + * + * @return A string containing a property value or null + * + * @throws IOException in case of an I/O error + */ + public String readProperty() throws IOException { + commentLines.clear(); + valueLines.clear(); + StringBuilder buffer = new StringBuilder(); + + while (true) { + String line = readLine(); + if (line == null) { + // EOF + return null; + } + + if (isCommentLine(line)) { + commentLines.add(line); + continue; + } + + boolean combine = checkCombineLines(line); + if (combine) { + line = line.substring(0, line.length() - 1); + } + valueLines.add(line); + while (line.length() > 0 && contains(WHITE_SPACE, line.charAt(0))) { + line = line.substring(1, line.length()); + } + buffer.append(line); + if (!combine) { + break; + } + } + return buffer.toString(); + } + + /** + * Parses the next property from the input stream and stores the found + * name and value in internal fields. These fields can be obtained using + * the provided getter methods. The return value indicates whether EOF + * was reached (false) or whether further properties are + * available (true). + * + * @return a flag if further properties are available + * @throws IOException if an error occurs + */ + public boolean nextProperty() throws IOException { + String line = readProperty(); + + if (line == null) { + return false; // EOF + } + + // parse the line + String[] property = parseProperty(line); + boolean typed = false; + if (maybeTyped && property[1].length() >= 2) { + typed = property[1].matches( + "\\s*[TILFDXSCBilfdxscb]?(\\[[\\S\\s]*\\]|\\([\\S\\s]*\\)|\\{[\\S\\s]*\\}|\"[\\S\\s]*\")\\s*"); + } + if (this.typed == null) { + this.typed = typed; + } else { + this.typed = this.typed & typed; + } + propertyName = unescapeJava(property[0]); + propertyValue = property[1]; + return true; + } + + /** + * Returns the comment lines that have been read for the last property. + * + * @return the comment lines for the last property returned by + * readProperty() + */ + public List getCommentLines() { + return commentLines; + } + + /** + * Returns the value lines that have been read for the last property. + * + * @return the raw value lines for the last property returned by + * readProperty() + */ + public List getValueLines() { + return valueLines; + } + + /** + * Returns the name of the last read property. This method can be called + * after {@link #nextProperty()} was invoked and its + * return value was true. + * + * @return the name of the last read property + */ + public String getPropertyName() { + return propertyName; + } + + /** + * Returns the value of the last read property. This method can be + * called after {@link #nextProperty()} was invoked and + * its return value was true. + * + * @return the value of the last read property + */ + public String getPropertyValue() { + return propertyValue; + } + + /** + * Checks if the passed in line should be combined with the following. + * This is true, if the line ends with an odd number of backslashes. + * + * @param line the line + * @return a flag if the lines should be combined + */ + private static boolean checkCombineLines(String line) { + int bsCount = 0; + for (int idx = line.length() - 1; idx >= 0 && line.charAt(idx) == '\\'; idx--) { + bsCount++; + } + + return bsCount % 2 != 0; + } + + /** + * Parse a property line and return the key and the value in an array. + * + * @param line the line to parse + * @return an array with the property's key and value + */ + private static String[] parseProperty(String line) { + // sorry for this spaghetti code, please replace it as soon as + // possible with a regexp when the Java 1.3 requirement is dropped + + String[] result = new String[2]; + StringBuilder key = new StringBuilder(); + StringBuilder value = new StringBuilder(); + + // state of the automaton: + // 0: key parsing + // 1: antislash found while parsing the key + // 2: separator crossing + // 3: white spaces + // 4: value parsing + int state = 0; + + for (int pos = 0; pos < line.length(); pos++) { + char c = line.charAt(pos); + + switch (state) { + case 0: + if (c == '\\') { + state = 1; + } else if (contains(WHITE_SPACE, c)) { + // switch to the separator crossing state + state = 2; + } else if (contains(SEPARATORS, c)) { + // switch to the value parsing state + state = 3; + } else { + key.append(c); + } + + break; + + case 1: + if (contains(SEPARATORS, c) || contains(WHITE_SPACE, c)) { + // this is an escaped separator or white space + key.append(c); + } else { + // another escaped character, the '\' is preserved + key.append('\\'); + key.append(c); + } + + // return to the key parsing state + state = 0; + + break; + + case 2: + if (contains(WHITE_SPACE, c)) { + // do nothing, eat all white spaces + state = 2; + } else if (contains(SEPARATORS, c)) { + // switch to the value parsing state + state = 3; + } else { + // any other character indicates we encoutered the beginning of the value + value.append(c); + + // switch to the value parsing state + state = 4; + } + + break; + + case 3: + if (contains(WHITE_SPACE, c)) { + // do nothing, eat all white spaces + state = 3; + } else { + // any other character indicates we encoutered the beginning of the value + value.append(c); + + // switch to the value parsing state + state = 4; + } + + break; + + case 4: + value.append(c); + break; + + default: + throw new IllegalStateException(); + } + } + + result[0] = key.toString(); + result[1] = value.toString(); + + return result; + } + } // class PropertiesReader + + /** + * This class is used to write properties lines. + */ + public static class PropertiesWriter extends FilterWriter { + private boolean typed; + + /** + * Constructor. + * + * @param writer a Writer object providing the underlying stream + */ + public PropertiesWriter(Writer writer, boolean typed) { + super(writer); + this.typed = typed; + } + + /** + * Writes the given property and its value. + * + * @param key the property key + * @param value the property value + * @throws IOException if an error occurs + */ + public void writeProperty(String key, String value) throws IOException { + write(escapeKey(key)); + write(" = "); + write(typed ? value : escapeJava(value)); + writeln(null); + } + + /** + * Helper method for writing a line with the platform specific line + * ending. + * + * @param s the content of the line (may be null) + * @throws IOException if an error occurs + */ + public void writeln(String s) throws IOException { + if (s != null) { + write(s); + } + write(LINE_SEPARATOR); + } + } // class PropertiesWriter + + /** + * TODO + */ + protected static class Layout { + + private List commentLines; + private List valueLines; + + public Layout() {} + + public Layout(List commentLines, List valueLines) { + this.commentLines = commentLines; + this.valueLines = valueLines; + } + + public List getCommentLines() { + return commentLines; + } + + public void setCommentLines(List commentLines) { + this.commentLines = commentLines; + } + + public List getValueLines() { + return valueLines; + } + + public void setValueLines(List valueLines) { + this.valueLines = valueLines; + } + + public void clearValue() { + this.valueLines = null; + } + } // class Layout +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/props/MavenPropertiesLoader.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/props/MavenPropertiesLoader.java new file mode 100644 index 0000000000..df9b0a6d5a --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/props/MavenPropertiesLoader.java @@ -0,0 +1,167 @@ +/* + * 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.maven.cling.props; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Enumeration; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.function.Function; + +import org.apache.maven.internal.impl.model.DefaultInterpolator; + +public class MavenPropertiesLoader { + + public static final String INCLUDES_PROPERTY = "${includes}"; // includes + + public static final String OVERRIDE_PREFIX = + "maven.override."; // prefix that marks that system property should override defaults. + + public static void loadProperties( + java.util.Properties properties, Path path, Function callback, boolean escape) + throws IOException { + MavenProperties sp = new MavenProperties(false); + if (Files.exists(path)) { + sp.load(path); + } + properties.forEach( + (k, v) -> sp.put(k.toString(), escape ? DefaultInterpolator.escape(v.toString()) : v.toString())); + loadIncludes(path, sp, callback); + substitute(sp, callback); + sp.forEach(properties::setProperty); + } + + public static void substitute(MavenProperties props, Function callback) { + for (Enumeration e = props.propertyNames(); e.hasMoreElements(); ) { + String name = (String) e.nextElement(); + String value = props.getProperty(name); + if (value == null) { + value = callback.apply(name); + } + if (name.startsWith(OVERRIDE_PREFIX)) { + String overrideName = name.substring(OVERRIDE_PREFIX.length()); + props.put(overrideName, substVars(value, name, props, callback)); + } else { + props.put(name, substVars(value, name, props, callback)); + } + } + props.keySet().removeIf(k -> k.startsWith(OVERRIDE_PREFIX)); + } + + private static MavenProperties loadPropertiesFile( + Path path, boolean failIfNotFound, Function callback) throws IOException { + MavenProperties configProps = new MavenProperties(null, false); + if (Files.exists(path) || failIfNotFound) { + configProps.load(path); + loadIncludes(path, configProps, callback); + trimValues(configProps); + } + return configProps; + } + + private static void loadIncludes(Path configProp, MavenProperties configProps, Function callback) + throws IOException { + String includes = configProps.get(INCLUDES_PROPERTY); + if (includes != null) { + includes = substVars(includes, INCLUDES_PROPERTY, configProps, callback); + StringTokenizer st = new StringTokenizer(includes, "?\",", true); + if (st.countTokens() > 0) { + String location; + do { + location = nextLocation(st); + if (location != null) { + boolean mandatory = true; + if (location.startsWith("?")) { + mandatory = false; + location = location.substring(1); + } + Path path = configProp.resolveSibling(location); + MavenProperties props = loadPropertiesFile(path, mandatory, s -> { + var v = callback.apply(s); + return v != null ? v : configProps.getProperty(s); + }); + configProps.putAll(props); + } + } while (location != null); + } + } + configProps.remove(INCLUDES_PROPERTY); + } + + private static void trimValues(MavenProperties configProps) { + configProps.replaceAll((k, v) -> v.trim()); + } + + private static String nextLocation(StringTokenizer st) { + boolean optional = false; + String retVal = null; + + if (st.countTokens() > 0) { + String tokenList = "?\","; + StringBuilder tokBuf = new StringBuilder(10); + String tok; + boolean inQuote = false; + boolean tokStarted = false; + boolean exit = false; + while ((st.hasMoreTokens()) && (!exit)) { + tok = st.nextToken(tokenList); + switch (tok) { + case "\"": + inQuote = !inQuote; + if (inQuote) { + tokenList = "\""; + } else { + tokenList = "\" "; + } + break; + case ",": + if (tokStarted) { + retVal = tokBuf.toString(); + tokStarted = false; + tokBuf = new StringBuilder(10); + exit = true; + } + break; + case "?": + optional = true; + break; + default: + tokStarted = true; + tokBuf.append(tok.trim()); + break; + } + } + + // Handle case where end of token stream and + // still got data + if ((!exit) && (tokStarted)) { + retVal = tokBuf.toString(); + } + } + + return optional ? "?" + retVal : retVal; + } + + public static String substVars( + String value, String name, Map props, Function callback) { + return DefaultInterpolator.substVars(value, name, null, props, callback, null, false); + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/AbstractMavenTransferListener.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/AbstractMavenTransferListener.java new file mode 100644 index 0000000000..6511072093 --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/AbstractMavenTransferListener.java @@ -0,0 +1,99 @@ +/* + * 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.maven.cling.transfer; + +import java.io.PrintStream; +import java.io.PrintWriter; + +import org.apache.maven.api.services.MessageBuilder; +import org.apache.maven.api.services.MessageBuilderFactory; +import org.eclipse.aether.transfer.AbstractTransferListener; +import org.eclipse.aether.transfer.TransferCancelledException; +import org.eclipse.aether.transfer.TransferEvent; +import org.eclipse.aether.transfer.TransferResource; + +/** + * AbstractMavenTransferListener + */ +public abstract class AbstractMavenTransferListener extends AbstractTransferListener { + public static final String STYLE = ".transfer:-faint"; + + protected final MessageBuilderFactory messageBuilderFactory; + protected final PrintWriter out; + + protected AbstractMavenTransferListener(MessageBuilderFactory messageBuilderFactory, PrintStream out) { + this(messageBuilderFactory, new PrintWriter(out)); + } + + protected AbstractMavenTransferListener(MessageBuilderFactory messageBuilderFactory, PrintWriter out) { + this.messageBuilderFactory = messageBuilderFactory; + this.out = out; + } + + @Override + public void transferInitiated(TransferEvent event) { + String action = event.getRequestType() == TransferEvent.RequestType.PUT ? "Uploading" : "Downloading"; + String direction = event.getRequestType() == TransferEvent.RequestType.PUT ? "to" : "from"; + + TransferResource resource = event.getResource(); + MessageBuilder message = messageBuilderFactory.builder(); + message.style(STYLE).append(action).append(' ').append(direction).append(' '); + message.resetStyle().append(resource.getRepositoryId()); + message.style(STYLE).append(": ").append(resource.getRepositoryUrl()); + message.resetStyle().append(resource.getResourceName()); + + out.println(message.toString()); + } + + @Override + public void transferCorrupted(TransferEvent event) throws TransferCancelledException { + TransferResource resource = event.getResource(); + // TODO This needs to be colorized + out.println("[WARNING] " + event.getException().getMessage() + " from " + resource.getRepositoryId() + " for " + + resource.getRepositoryUrl() + resource.getResourceName()); + } + + @Override + public void transferSucceeded(TransferEvent event) { + String action = (event.getRequestType() == TransferEvent.RequestType.PUT ? "Uploaded" : "Downloaded"); + String direction = event.getRequestType() == TransferEvent.RequestType.PUT ? "to" : "from"; + + TransferResource resource = event.getResource(); + long contentLength = event.getTransferredBytes(); + FileSizeFormat format = new FileSizeFormat(); + + MessageBuilder message = messageBuilderFactory.builder(); + message.append(action).style(STYLE).append(' ').append(direction).append(' '); + message.resetStyle().append(resource.getRepositoryId()); + message.style(STYLE).append(": ").append(resource.getRepositoryUrl()); + message.resetStyle().append(resource.getResourceName()); + message.style(STYLE).append(" (").append(format.format(contentLength)); + + long duration = System.currentTimeMillis() - resource.getTransferStartTime(); + if (duration > 0L) { + double bytesPerSecond = contentLength / (duration / 1000.0); + message.append(" at "); + format.format(message, (long) bytesPerSecond); + message.append("/s"); + } + + message.append(')').resetStyle(); + out.println(message.toString()); + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/ConsoleMavenTransferListener.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/ConsoleMavenTransferListener.java new file mode 100644 index 0000000000..46a17908ec --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/ConsoleMavenTransferListener.java @@ -0,0 +1,157 @@ +/* + * 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.maven.cling.transfer; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.maven.api.services.MessageBuilderFactory; +import org.eclipse.aether.transfer.TransferCancelledException; +import org.eclipse.aether.transfer.TransferEvent; +import org.eclipse.aether.transfer.TransferResource; + +/** + * Console download progress meter. + *

+ * This listener is not thread-safe and should be wrapped in the {@link SimplexTransferListener} in a multi-threaded scenario. + */ +public class ConsoleMavenTransferListener extends AbstractMavenTransferListener { + + private final Map transfers = new LinkedHashMap<>(); + private final FileSizeFormat format = new FileSizeFormat(); // use in a synchronized fashion + private final StringBuilder buffer = new StringBuilder(128); // use in a synchronized fashion + + private final boolean printResourceNames; + private int lastLength; + + public ConsoleMavenTransferListener( + MessageBuilderFactory messageBuilderFactory, PrintStream out, boolean printResourceNames) { + this(messageBuilderFactory, new PrintWriter(out), printResourceNames); + } + + public ConsoleMavenTransferListener( + MessageBuilderFactory messageBuilderFactory, PrintWriter out, boolean printResourceNames) { + super(messageBuilderFactory, out); + this.printResourceNames = printResourceNames; + } + + @Override + public void transferInitiated(TransferEvent event) { + overridePreviousTransfer(event); + + super.transferInitiated(event); + } + + @Override + public void transferCorrupted(TransferEvent event) throws TransferCancelledException { + overridePreviousTransfer(event); + + super.transferCorrupted(event); + } + + @Override + public void transferProgressed(TransferEvent event) throws TransferCancelledException { + TransferResource resource = event.getResource(); + transfers.put( + new TransferResourceIdentifier(resource), + new TransferResourceAndSize(resource, event.getTransferredBytes())); + + buffer.append("Progress (").append(transfers.size()).append("): "); + + Iterator entries = transfers.values().iterator(); + while (entries.hasNext()) { + TransferResourceAndSize entry = entries.next(); + // just in case, make sure 0 <= complete <= total + long complete = Math.max(0, entry.transferredBytes); + long total = Math.max(complete, entry.resource.getContentLength()); + + String resourceName = entry.resource.getResourceName(); + + if (printResourceNames) { + int idx = resourceName.lastIndexOf('/'); + + if (idx < 0) { + buffer.append(resourceName); + } else { + buffer.append(resourceName, idx + 1, resourceName.length()); + } + buffer.append(" ("); + } + + format.formatProgress(buffer, complete, total); + + if (printResourceNames) { + buffer.append(")"); + } + + if (entries.hasNext()) { + buffer.append(" | "); + } + } + + int pad = lastLength - buffer.length(); + lastLength = buffer.length(); + pad(buffer, pad); + buffer.append('\r'); + out.print(buffer); + out.flush(); + buffer.setLength(0); + } + + private void pad(StringBuilder buffer, int spaces) { + String block = " "; + while (spaces > 0) { + int n = Math.min(spaces, block.length()); + buffer.append(block, 0, n); + spaces -= n; + } + } + + @Override + public void transferSucceeded(TransferEvent event) { + transfers.remove(new TransferResourceIdentifier(event.getResource())); + overridePreviousTransfer(event); + + super.transferSucceeded(event); + } + + @Override + public void transferFailed(TransferEvent event) { + transfers.remove(new TransferResourceIdentifier(event.getResource())); + overridePreviousTransfer(event); + + super.transferFailed(event); + } + + private void overridePreviousTransfer(TransferEvent event) { + if (lastLength > 0) { + pad(buffer, lastLength); + buffer.append('\r'); + out.print(buffer); + out.flush(); + lastLength = 0; + buffer.setLength(0); + } + } + + private record TransferResourceAndSize(TransferResource resource, long transferredBytes) {} +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/FileSizeFormat.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/FileSizeFormat.java new file mode 100644 index 0000000000..a46e0b035b --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/FileSizeFormat.java @@ -0,0 +1,204 @@ +/* + * 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.maven.cling.transfer; + +import org.apache.maven.api.services.MessageBuilder; + +/** + * Formats file size with the associated SI prefix + * (GB, MB, kB) and using the patterns #0.0 for numbers between 1 and 10 + * and ###0 for numbers between 10 and 1000+ by default. + * + * @see https://en.wikipedia.org/wiki/Metric_prefix + * @see https://en.wikipedia.org/wiki/Binary_prefix + * @see https://en.wikipedia.org/wiki/Octet_(computing) + */ +public class FileSizeFormat { + public enum ScaleUnit { + BYTE { + @Override + public long bytes() { + return 1L; + } + + @Override + public String symbol() { + return "B"; + } + }, + KILOBYTE { + @Override + public long bytes() { + return 1000L; + } + + @Override + public String symbol() { + return "kB"; + } + }, + MEGABYTE { + @Override + public long bytes() { + return KILOBYTE.bytes() * KILOBYTE.bytes(); + } + + @Override + public String symbol() { + return "MB"; + } + }, + GIGABYTE { + @Override + public long bytes() { + return MEGABYTE.bytes() * KILOBYTE.bytes(); + } + ; + + @Override + public String symbol() { + return "GB"; + } + }; + + public abstract long bytes(); + + public abstract String symbol(); + + public static ScaleUnit getScaleUnit(long size) { + if (size < 0L) { + throw new IllegalArgumentException("file size cannot be negative: " + size); + } + + if (size >= GIGABYTE.bytes()) { + return GIGABYTE; + } else if (size >= MEGABYTE.bytes()) { + return MEGABYTE; + } else if (size >= KILOBYTE.bytes()) { + return KILOBYTE; + } else { + return BYTE; + } + } + } + + public String format(long size) { + return format(size, null); + } + + public String format(long size, ScaleUnit unit) { + return format(size, unit, false); + } + + public String format(long size, ScaleUnit unit, boolean omitSymbol) { + StringBuilder sb = new StringBuilder(); + format(sb, size, unit, omitSymbol); + return sb.toString(); + } + + public void format(StringBuilder builder, long size) { + format(builder, size, null, false); + } + + public void format(StringBuilder builder, long size, ScaleUnit unit) { + format(builder, size, unit, false); + } + + private void format(StringBuilder builder, long size, ScaleUnit unit, boolean omitSymbol) { + if (size < 0L) { + throw new IllegalArgumentException("file size cannot be negative: " + size); + } + if (unit == null) { + unit = ScaleUnit.getScaleUnit(size); + } + + double scaledSize = (double) size / unit.bytes(); + + if (unit == ScaleUnit.BYTE) { + builder.append(size); + } else if (scaledSize < 0.05d || scaledSize >= 10.0d) { + builder.append(Math.round(scaledSize)); + } else { + builder.append(Math.round(scaledSize * 10d) / 10d); + } + + if (!omitSymbol) { + builder.append(" ").append(unit.symbol()); + } + } + + public void format(MessageBuilder builder, long size) { + format(builder, size, null, false); + } + + public void format(MessageBuilder builder, long size, ScaleUnit unit) { + format(builder, size, unit, false); + } + + private void format(MessageBuilder builder, long size, ScaleUnit unit, boolean omitSymbol) { + if (size < 0L) { + throw new IllegalArgumentException("file size cannot be negative: " + size); + } + if (unit == null) { + unit = ScaleUnit.getScaleUnit(size); + } + + double scaledSize = (double) size / unit.bytes(); + + if (unit == ScaleUnit.BYTE) { + builder.append(Long.toString(size)); + } else if (scaledSize < 0.05d || scaledSize >= 10.0d) { + builder.append(Long.toString(Math.round(scaledSize))); + } else { + builder.append(Double.toString(Math.round(scaledSize * 10d) / 10d)); + } + + if (!omitSymbol) { + builder.append(" ").append(unit.symbol()); + } + } + + public String formatProgress(long progressedSize, long size) { + StringBuilder sb = new StringBuilder(); + formatProgress(sb, progressedSize, size); + return sb.toString(); + } + + public void formatProgress(StringBuilder builder, long progressedSize, long size) { + if (progressedSize < 0L) { + throw new IllegalArgumentException("progressed file size cannot be negative: " + size); + } + if (size >= 0 && progressedSize > size) { + throw new IllegalArgumentException( + "progressed file size cannot be greater than size: " + progressedSize + " > " + size); + } + + if (size >= 0L && progressedSize != size) { + ScaleUnit unit = ScaleUnit.getScaleUnit(size); + format(builder, progressedSize, unit, true); + builder.append("/"); + format(builder, size, unit, false); + } else { + ScaleUnit unit = ScaleUnit.getScaleUnit(progressedSize); + + format(builder, progressedSize, unit, false); + } + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/QuietMavenTransferListener.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/QuietMavenTransferListener.java new file mode 100644 index 0000000000..56d357b612 --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/QuietMavenTransferListener.java @@ -0,0 +1,25 @@ +/* + * 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.maven.cling.transfer; + +import org.eclipse.aether.transfer.AbstractTransferListener; + +/** + */ +public class QuietMavenTransferListener extends AbstractTransferListener {} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/SimplexTransferListener.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/SimplexTransferListener.java new file mode 100644 index 0000000000..6d8bc2044b --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/SimplexTransferListener.java @@ -0,0 +1,230 @@ +/* + * 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.maven.cling.transfer; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; + +import org.eclipse.aether.transfer.AbstractTransferListener; +import org.eclipse.aether.transfer.TransferCancelledException; +import org.eclipse.aether.transfer.TransferEvent; +import org.eclipse.aether.transfer.TransferListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static java.util.Objects.requireNonNull; + +/** + * A {@link TransferListener} implementation that wraps another delegate {@link TransferListener} but makes it run + * on single thread, keeping the listener logic simple. This listener also blocks on last transfer event to allow + * output to perform possible cleanup. It spawns a daemon thread to consume queued events that may fall in even + * concurrently. + * + * @since 4.0.0 + */ +public final class SimplexTransferListener extends AbstractTransferListener { + private static final Logger LOGGER = LoggerFactory.getLogger(SimplexTransferListener.class); + private static final int QUEUE_SIZE = 1024; + private static final int BATCH_MAX_SIZE = 500; + private final TransferListener delegate; + private final int batchMaxSize; + private final boolean blockOnLastEvent; + private final ArrayBlockingQueue eventQueue; + + /** + * Constructor that makes passed in delegate run on single thread, and will block on last event. + */ + public SimplexTransferListener(TransferListener delegate) { + this(delegate, QUEUE_SIZE, BATCH_MAX_SIZE, true); + } + + /** + * Constructor that may alter behaviour of this listener. + * + * @param delegate The delegate that should run on single thread. + * @param queueSize The event queue size (default {@code 1024}). + * @param batchMaxSize The maximum batch size delegate should receive (default {@code 500}). + * @param blockOnLastEvent Should this listener block on last transfer end (completed or corrupted) block? (default {@code true}). + */ + public SimplexTransferListener( + TransferListener delegate, int queueSize, int batchMaxSize, boolean blockOnLastEvent) { + this.delegate = requireNonNull(delegate); + if (queueSize < 1 || batchMaxSize < 1) { + throw new IllegalArgumentException("Queue and batch sizes must be greater than 1"); + } + this.batchMaxSize = batchMaxSize; + this.blockOnLastEvent = blockOnLastEvent; + + this.eventQueue = new ArrayBlockingQueue<>(queueSize); + Thread updater = new Thread(this::feedConsumer); + updater.setDaemon(true); + updater.start(); + } + + public TransferListener getDelegate() { + return delegate; + } + + private void feedConsumer() { + final ArrayList batch = new ArrayList<>(batchMaxSize); + try { + while (true) { + batch.clear(); + if (eventQueue.drainTo(batch, BATCH_MAX_SIZE) == 0) { + batch.add(eventQueue.take()); + } + demux(batch); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + private void demux(List exchanges) { + for (Exchange exchange : exchanges) { + exchange.process(transferEvent -> { + TransferEvent.EventType type = transferEvent.getType(); + try { + switch (type) { + case INITIATED: + delegate.transferInitiated(transferEvent); + break; + case STARTED: + delegate.transferStarted(transferEvent); + break; + case PROGRESSED: + delegate.transferProgressed(transferEvent); + break; + case CORRUPTED: + delegate.transferCorrupted(transferEvent); + break; + case SUCCEEDED: + delegate.transferSucceeded(transferEvent); + break; + case FAILED: + delegate.transferFailed(transferEvent); + break; + default: + LOGGER.warn("Invalid TransferEvent.EventType={}; ignoring it", type); + } + } catch (TransferCancelledException e) { + ongoing.put(new TransferResourceIdentifier(transferEvent.getResource()), Boolean.FALSE); + } + }); + } + } + + private void put(TransferEvent event, boolean last) { + try { + Exchange exchange; + if (blockOnLastEvent && last) { + exchange = new BlockingExchange(event); + } else { + exchange = new Exchange(event); + } + eventQueue.put(exchange); + exchange.waitForProcessed(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + private final ConcurrentHashMap ongoing = new ConcurrentHashMap<>(); + + @Override + public void transferInitiated(TransferEvent event) { + ongoing.putIfAbsent(new TransferResourceIdentifier(event.getResource()), Boolean.TRUE); + put(event, false); + } + + @Override + public void transferStarted(TransferEvent event) throws TransferCancelledException { + if (ongoing.get(new TransferResourceIdentifier(event.getResource())) == Boolean.FALSE) { + throw new TransferCancelledException(); + } + put(event, false); + } + + @Override + public void transferProgressed(TransferEvent event) throws TransferCancelledException { + if (ongoing.get(new TransferResourceIdentifier(event.getResource())) == Boolean.FALSE) { + throw new TransferCancelledException(); + } + put(event, false); + } + + @Override + public void transferCorrupted(TransferEvent event) throws TransferCancelledException { + if (ongoing.get(new TransferResourceIdentifier(event.getResource())) == Boolean.FALSE) { + throw new TransferCancelledException(); + } + put(event, false); + } + + @Override + public void transferSucceeded(TransferEvent event) { + ongoing.remove(new TransferResourceIdentifier(event.getResource())); + put(event, ongoing.isEmpty()); + } + + @Override + public void transferFailed(TransferEvent event) { + ongoing.remove(new TransferResourceIdentifier(event.getResource())); + put(event, ongoing.isEmpty()); + } + + private static class Exchange { + private final TransferEvent event; + + private Exchange(TransferEvent event) { + this.event = event; + } + + public void process(Consumer consumer) { + consumer.accept(event); + } + + public void waitForProcessed() throws InterruptedException { + // nothing, is async + } + } + + private static class BlockingExchange extends Exchange { + private final CountDownLatch latch = new CountDownLatch(1); + + private BlockingExchange(TransferEvent event) { + super(event); + } + + @Override + public void process(Consumer consumer) { + super.process(consumer); + latch.countDown(); + } + + @Override + public void waitForProcessed() throws InterruptedException { + latch.await(); + } + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/Slf4jMavenTransferListener.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/Slf4jMavenTransferListener.java new file mode 100644 index 0000000000..b5ab1b6574 --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/Slf4jMavenTransferListener.java @@ -0,0 +1,97 @@ +/* + * 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.maven.cling.transfer; + +import org.eclipse.aether.transfer.AbstractTransferListener; +import org.eclipse.aether.transfer.TransferCancelledException; +import org.eclipse.aether.transfer.TransferEvent; +import org.eclipse.aether.transfer.TransferResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Slf4jMavenTransferListener + */ +public class Slf4jMavenTransferListener extends AbstractTransferListener { + + protected final Logger out; + + public Slf4jMavenTransferListener() { + this.out = LoggerFactory.getLogger(Slf4jMavenTransferListener.class); + } + + // TODO should we deprecate? + public Slf4jMavenTransferListener(Logger out) { + this.out = out; + } + + @Override + public void transferInitiated(TransferEvent event) { + String action = event.getRequestType() == TransferEvent.RequestType.PUT ? "Uploading" : "Downloading"; + String direction = event.getRequestType() == TransferEvent.RequestType.PUT ? "to" : "from"; + + TransferResource resource = event.getResource(); + StringBuilder message = new StringBuilder(); + message.append(action).append(' ').append(direction).append(' ').append(resource.getRepositoryId()); + message.append(": "); + message.append(resource.getRepositoryUrl()).append(resource.getResourceName()); + + out.info(message.toString()); + } + + @Override + public void transferCorrupted(TransferEvent event) throws TransferCancelledException { + TransferResource resource = event.getResource(); + out.warn( + "{} from {} for {}{}", + event.getException().getMessage(), + resource.getRepositoryId(), + resource.getRepositoryUrl(), + resource.getResourceName()); + } + + @Override + public void transferSucceeded(TransferEvent event) { + String action = (event.getRequestType() == TransferEvent.RequestType.PUT ? "Uploaded" : "Downloaded"); + String direction = event.getRequestType() == TransferEvent.RequestType.PUT ? "to" : "from"; + + TransferResource resource = event.getResource(); + long contentLength = event.getTransferredBytes(); + FileSizeFormat format = new FileSizeFormat(); + + StringBuilder message = new StringBuilder(); + message.append(action).append(' ').append(direction).append(' ').append(resource.getRepositoryId()); + message.append(": "); + message.append(resource.getRepositoryUrl()) + .append(resource.getResourceName()) + .append(" ("); + format.format(message, contentLength); + + long duration = System.currentTimeMillis() - resource.getTransferStartTime(); + if (duration > 0L) { + double bytesPerSecond = contentLength / (duration / 1000.0); + message.append(" at "); + format.format(message, (long) bytesPerSecond); + message.append("/s"); + } + + message.append(')'); + out.info(message.toString()); + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/TransferResourceIdentifier.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/TransferResourceIdentifier.java new file mode 100644 index 0000000000..04af5e51a7 --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/TransferResourceIdentifier.java @@ -0,0 +1,35 @@ +/* + * 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.maven.cling.transfer; + +import java.nio.file.Path; + +import org.apache.maven.api.annotations.Nullable; +import org.eclipse.aether.transfer.TransferResource; + +/** + * Immutable identifier of a {@link TransferResource}. + * The {@link TransferResource} is not immutable and does not implement {@code Objects#equals} and {@code Objects#hashCode} methods, + * making it not very suitable for usage in collections. + */ +record TransferResourceIdentifier(String repositoryId, String repositoryUrl, String resourceName, @Nullable Path file) { + TransferResourceIdentifier(TransferResource resource) { + this(resource.getRepositoryId(), resource.getRepositoryUrl(), resource.getResourceName(), resource.getPath()); + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/utils/CLIReportingUtils.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/utils/CLIReportingUtils.java new file mode 100644 index 0000000000..d18f0a7c18 --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/utils/CLIReportingUtils.java @@ -0,0 +1,203 @@ +/* + * 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.maven.cling.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Properties; + +import org.apache.maven.jline.MessageUtils; +import org.codehaus.plexus.util.Os; +import org.slf4j.Logger; + +/** + * Utility class used to report errors, statistics, application version info, etc. + * + */ +public final class CLIReportingUtils { + // CHECKSTYLE_OFF: MagicNumber + public static final long MB = 1024 * 1024; + + private static final long ONE_SECOND = 1000L; + + private static final long ONE_MINUTE = 60 * ONE_SECOND; + + private static final long ONE_HOUR = 60 * ONE_MINUTE; + + private static final long ONE_DAY = 24 * ONE_HOUR; + // CHECKSTYLE_ON: MagicNumber + + public static final String BUILD_VERSION_PROPERTY = "version"; + + public static String showVersion() { + return showVersion(null, null); + } + + public static String showVersion(String commandLine, String terminal) { + final String ls = System.lineSeparator(); + Properties properties = getBuildProperties(); + StringBuilder version = new StringBuilder(256); + version.append(MessageUtils.builder().strong(createMavenVersionString(properties))) + .append(ls); + version.append(reduce(properties.getProperty("distributionShortName") + " home: " + + System.getProperty("maven.home", ""))) + .append(ls); + version.append("Java version: ") + .append(System.getProperty("java.version", "")) + .append(", vendor: ") + .append(System.getProperty("java.vendor", "")) + .append(", runtime: ") + .append(System.getProperty("java.home", "")) + .append(ls); + version.append("Default locale: ") + .append(Locale.getDefault()) + .append(", platform encoding: ") + .append(System.getProperty("file.encoding", "")) + .append(ls); + version.append("OS name: \"") + .append(Os.OS_NAME) + .append("\", version: \"") + .append(Os.OS_VERSION) + .append("\", arch: \"") + .append(Os.OS_ARCH) + .append("\", family: \"") + .append(Os.OS_FAMILY) + .append('\"'); + // Add process information using modern Java API + if (commandLine != null) { + version.append(ls).append("Command line: ").append(commandLine); + } + if (terminal != null) { + version.append(ls).append("Terminal: ").append(terminal); + } + return version.toString(); + } + + public static String showVersionMinimal() { + Properties properties = getBuildProperties(); + String version = reduce(properties.getProperty(BUILD_VERSION_PROPERTY)); + return (version != null ? version : ""); + } + + /** + * Create a human-readable string containing the Maven version, buildnumber, and time of build + * + * @param buildProperties The build properties + * @return Readable build info + */ + public static String createMavenVersionString(Properties buildProperties) { + String timestamp = reduce(buildProperties.getProperty("timestamp")); + String version = reduce(buildProperties.getProperty(BUILD_VERSION_PROPERTY)); + String rev = reduce(buildProperties.getProperty("buildNumber")); + String distributionName = reduce(buildProperties.getProperty("distributionName")); + + String msg = distributionName + " "; + msg += (version != null ? version : ""); + if (rev != null || timestamp != null) { + msg += " ("; + msg += (rev != null ? rev : ""); + if (timestamp != null && !timestamp.isEmpty()) { + String ts = formatTimestamp(Long.parseLong(timestamp)); + msg += (rev != null ? "; " : "") + ts; + } + msg += ")"; + } + return msg; + } + + private static String reduce(String s) { + return (s != null ? (s.startsWith("${") && s.endsWith("}") ? null : s) : null); + } + + public static Properties getBuildProperties() { + Properties properties = new Properties(); + + try (InputStream resourceAsStream = + CLIReportingUtils.class.getResourceAsStream("/org/apache/maven/messages/build.properties")) { + + if (resourceAsStream != null) { + properties.load(resourceAsStream); + } + } catch (IOException e) { + System.err.println("Unable determine version from JAR file: " + e.getMessage()); + } + + return properties; + } + + public static void showError(Logger logger, String message, Throwable e, boolean showStackTrace) { + if (logger == null) { + System.err.println(message); + if (showStackTrace && e != null) { + e.printStackTrace(System.err); + } + return; + } + if (showStackTrace) { + logger.error(message, e); + } else { + logger.error(message); + + if (e != null) { + logger.error(e.getMessage()); + + for (Throwable cause = e.getCause(); + cause != null && cause != cause.getCause(); + cause = cause.getCause()) { + logger.error("Caused by: {}", cause.getMessage()); + } + } + } + } + + public static String formatTimestamp(long timestamp) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); + return sdf.format(new Date(timestamp)); + } + + public static String formatDuration(long duration) { + // CHECKSTYLE_OFF: MagicNumber + long ms = duration % 1000; + long s = (duration / ONE_SECOND) % 60; + long m = (duration / ONE_MINUTE) % 60; + long h = (duration / ONE_HOUR) % 24; + long d = duration / ONE_DAY; + // CHECKSTYLE_ON: MagicNumber + + String format; + if (d > 0) { + // Length 11+ chars + format = "%d d %02d:%02d h"; + } else if (h > 0) { + // Length 7 chars + format = "%2$02d:%3$02d h"; + } else if (m > 0) { + // Length 9 chars + format = "%3$02d:%4$02d min"; + } else { + // Length 7-8 chars + format = "%4$d.%5$03d s"; + } + + return String.format(format, d, h, m, s, ms); + } +} diff --git a/compat/maven-embedder/src/main/resources/META-INF/maven/slf4j-configuration.properties b/impl/maven-cli/src/main/resources/META-INF/maven/slf4j-configuration.properties similarity index 74% rename from compat/maven-embedder/src/main/resources/META-INF/maven/slf4j-configuration.properties rename to impl/maven-cli/src/main/resources/META-INF/maven/slf4j-configuration.properties index ba7b675ea9..369b0e6a26 100644 --- a/compat/maven-embedder/src/main/resources/META-INF/maven/slf4j-configuration.properties +++ b/impl/maven-cli/src/main/resources/META-INF/maven/slf4j-configuration.properties @@ -17,7 +17,7 @@ # key = Slf4j effective logger factory implementation # value = corresponding o.a.m.cli.logging.Slf4jConfiguration class -org.slf4j.impl.SimpleLoggerFactory=org.apache.maven.cli.logging.impl.MavenSimpleConfiguration -org.apache.maven.slf4j.MavenLoggerFactory=org.apache.maven.cli.logging.impl.MavenSimpleConfiguration -org.apache.logging.slf4j.Log4jLoggerFactory=org.apache.maven.cli.logging.impl.Log4j2Configuration -ch.qos.logback.classic.LoggerContext=org.apache.maven.cli.logging.impl.LogbackConfiguration +org.slf4j.impl.SimpleLoggerFactory=org.apache.maven.cling.logging.impl.MavenSimpleConfiguration +org.apache.maven.slf4j.MavenLoggerFactory=org.apache.maven.cling.logging.impl.MavenSimpleConfiguration +org.apache.logging.slf4j.Log4jLoggerFactory=org.apache.maven.cling.logging.impl.Log4j2Configuration +ch.qos.logback.classic.LoggerContext=org.apache.maven.cling.logging.impl.LogbackConfiguration