This is an automated email from the ASF dual-hosted git repository.

kwin pushed a commit to branch feature/enhanced-transport-listener
in repository https://gitbox.apache.org/repos/asf/maven-resolver.git

commit 7fa703be78eafd0cbe86238113c72f74906f1ee3
Author: Konrad Windszus <[email protected]>
AuthorDate: Mon Jan 26 19:47:18 2026 +0100

    Expose additional transport details to TransportListener
    
    For HTTP Transporters this is:
    - HTTP Version
    - SSL Protocol (only HTTPS)
    - SSL Cipher Suite (only HTTPS)
    - Compression Algorithm (if used)
    - Transport Size (if it differs from data size)
    
    This closes #1761
---
 .../connector/transport/AbstractTransporter.java   | 62 +++++++++++++++++++++-
 .../spi/connector/transport/TransportListener.java | 30 +++++++++++
 .../connector/transport/http/HttpTransporter.java  | 32 ++++++++++-
 .../test/util/http/RecordingTransportListener.java |  6 ++-
 .../aether/transport/apache/ApacheTransporter.java | 17 +++++-
 .../aether/transport/jdk/JdkTransporter.java       | 23 +++++++-
 .../aether/transport/jetty/JettyTransporter.java   | 24 ++++++++-
 7 files changed, 184 insertions(+), 10 deletions(-)

diff --git 
a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/AbstractTransporter.java
 
b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/AbstractTransporter.java
index 7c4f680d6..2f665a257 100644
--- 
a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/AbstractTransporter.java
+++ 
b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/AbstractTransporter.java
@@ -22,6 +22,8 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.eclipse.aether.transfer.TransferCancelledException;
@@ -87,11 +89,42 @@ public abstract class AbstractTransporter implements 
Transporter {
      *            download starts at the first byte of the resource.
      * @throws IOException If the transfer encountered an I/O error.
      * @throws TransferCancelledException If the transfer was cancelled.
+     * @deprecated Use {@link #utilGet(GetTask, InputStream, boolean, long, 
boolean, Map)} instead.
      */
+    @Deprecated
     protected void utilGet(GetTask task, InputStream is, boolean close, long 
length, boolean resume)
             throws IOException, TransferCancelledException {
+        utilGet(task, is, close, length, resume, Collections.emptyMap());
+    }
+
+    /**
+     * Performs stream-based I/O for the specified download task and notifies 
the configured transport listener.
+     * Subclasses might want to invoke this utility method from within their 
{@link #implGet(GetTask)} to avoid
+     * boilerplate I/O code.
+     *
+     * @param task The download to perform, must not be {@code null}.
+     * @param is The input stream to download the data from, must not be 
{@code null}.
+     * @param close {@code true} if the supplied input stream should be 
automatically closed, {@code false} to leave the
+     *            stream open.
+     * @param length The size in bytes of the downloaded resource or {@code 
-1} if unknown, not to be confused with the
+     *            length of the supplied input stream which might be smaller 
if the download is resumed.
+     * @param resume {@code true} if the download resumes from {@link 
GetTask#getResumeOffset()}, {@code false} if the
+     *            download starts at the first byte of the resource.
+     * @param transportProperties the transport properties connected with this 
download. May be empty.
+     * @throws IOException If the transfer encountered an I/O error.
+     * @throws TransferCancelledException If the transfer was cancelled.
+     * @since NEXT
+     */
+    protected void utilGet(
+            GetTask task,
+            InputStream is,
+            boolean close,
+            long length,
+            boolean resume,
+            Map<TransportListener.TransportPropertyKey, Object> 
transportProperties)
+            throws IOException, TransferCancelledException {
         try (OutputStream os = task.newOutputStream(resume)) {
-            task.getListener().transportStarted(resume ? 
task.getResumeOffset() : 0L, length);
+            task.getListener().transportStarted(resume ? 
task.getResumeOffset() : 0L, length, transportProperties);
             copy(os, is, task.getListener());
         } finally {
             if (close) {
@@ -126,11 +159,36 @@ public abstract class AbstractTransporter implements 
Transporter {
      *            the stream open.
      * @throws IOException If the transfer encountered an I/O error.
      * @throws TransferCancelledException If the transfer was cancelled.
+     * @deprecated Use {@link #utilPut(PutTask, OutputStream, boolean, Map)} 
instead.
      */
+    @Deprecated
     protected void utilPut(PutTask task, OutputStream os, boolean close)
             throws IOException, TransferCancelledException {
+        utilPut(task, os, close, Collections.emptyMap());
+    }
+
+    /**
+     * Performs stream-based I/O for the specified upload task and notifies 
the configured transport listener.
+     * Subclasses might want to invoke this utility method from within their 
{@link #implPut(PutTask)} to avoid
+     * boilerplate I/O code.
+     *
+     * @param task The upload to perform, must not be {@code null}.
+     * @param os The output stream to upload the data to, must not be {@code 
null}.
+     * @param close {@code true} if the supplied output stream should be 
automatically closed, {@code false} to leave
+     *            the stream open.
+     * @param transportProperties the transport properties connected with this 
upload. May be empty.
+     * @throws IOException If the transfer encountered an I/O error.
+     * @throws TransferCancelledException If the transfer was cancelled.
+     * @since NEXT
+     */
+    protected void utilPut(
+            PutTask task,
+            OutputStream os,
+            boolean close,
+            Map<TransportListener.TransportPropertyKey, Object> 
transportProperties)
+            throws IOException, TransferCancelledException {
         try (InputStream is = task.newInputStream()) {
-            task.getListener().transportStarted(0, task.getDataLength());
+            task.getListener().transportStarted(0, task.getDataLength(), 
transportProperties);
             copy(os, is, task.getListener());
         } finally {
             if (close) {
diff --git 
a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/TransportListener.java
 
b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/TransportListener.java
index b6e60645a..e05acc840 100644
--- 
a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/TransportListener.java
+++ 
b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/TransportListener.java
@@ -19,6 +19,8 @@
 package org.eclipse.aether.spi.connector.transport;
 
 import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.Map;
 
 import org.eclipse.aether.transfer.TransferCancelledException;
 
@@ -34,6 +36,8 @@ import org.eclipse.aether.transfer.TransferCancelledException;
  */
 public abstract class TransportListener {
 
+    public interface TransportPropertyKey {}
+
     /**
      * Enables subclassing.
      */
@@ -45,8 +49,26 @@ public abstract class TransportListener {
      *
      * @param dataOffset The byte offset in the resource at which the transfer 
starts, must not be negative.
      * @param dataLength The total number of bytes in the resource or {@code 
-1} if the length is unknown.
+     * @param transportProperties The transport properties associated with 
this transfer, may be empty. The keys are transporter specific and the value 
types are key specific.
      * @throws TransferCancelledException If the transfer should be aborted.
+     * @since NEXT
      */
+    public void transportStarted(
+            long dataOffset, long dataLength, Map<TransportPropertyKey, 
Object> transportProperties)
+            throws TransferCancelledException {
+        transportStarted(dataOffset, dataLength);
+    }
+
+    /**
+     * Notifies the listener about the start of the data transfer. This event 
may arise more than once if the transfer
+     * needs to be restarted (e.g. after an authentication failure).
+     *
+     * @param dataOffset The byte offset in the resource at which the transfer 
starts, must not be negative.
+     * @param dataLength The total number of bytes in the resource or {@code 
-1} if the length is unknown.
+     * @throws TransferCancelledException If the transfer should be aborted.
+     * @deprecated use {@link #transportStarted(long, long, Map)} instead
+     */
+    @Deprecated
     public void transportStarted(long dataOffset, long dataLength) throws 
TransferCancelledException {}
 
     /**
@@ -57,4 +79,12 @@ public abstract class TransportListener {
      * @throws TransferCancelledException If the transfer should be aborted.
      */
     public void transportProgressed(ByteBuffer data) throws 
TransferCancelledException {}
+
+    /**
+     * Notifies the listener about the completion of the data transfer.
+     *
+     * @param details The collection of detail strings about the transfer, may 
be empty but not {@code null}.
+     * @throws TransferCancelledException If the transfer should be aborted.
+     */
+    public void transportCompleted(Collection<String> details) throws 
TransferCancelledException {}
 }
diff --git 
a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/HttpTransporter.java
 
b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/HttpTransporter.java
index 954ce8891..5160087f7 100644
--- 
a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/HttpTransporter.java
+++ 
b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/HttpTransporter.java
@@ -18,6 +18,7 @@
  */
 package org.eclipse.aether.spi.connector.transport.http;
 
+import org.eclipse.aether.spi.connector.transport.TransportListener;
 import org.eclipse.aether.spi.connector.transport.Transporter;
 
 /**
@@ -25,4 +26,33 @@ import 
org.eclipse.aether.spi.connector.transport.Transporter;
  *
  * @since 2.0.0
  */
-public interface HttpTransporter extends Transporter {}
+public interface HttpTransporter extends Transporter {
+
+    /**
+     * Transport property keys specific to HTTP transporters.
+     * @see 
org.eclipse.aether.spi.connector.transport.TransporterListener#transportStarted(long,
 long, java.util.Map)
+     */
+    enum HttpTransportPropertyKey implements 
TransportListener.TransportPropertyKey {
+        /**
+         * Transport property key for HTTP version. Value is a String 
representing the HTTP version used (e.g., "HTTP/1.1", "HTTP/2").
+         */
+        HTTP_VERSION,
+        /**
+         * Transport property key for SSL protocol. Value is a String 
representing the SSL protocol used (e.g., "TLSv1.2", "TLSv1.3").
+         */
+        SSL_PROTOCOL,
+        /**
+         * Transport property key for SSL cipher suite. Value is a String 
representing the SSL cipher suite used (e.g., 
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256").
+         */
+        SSL_CIPHER_SUITE,
+        /**
+         * Transport property key for compression algorithm. Value is a String 
representing the compression algorithm used (e.g., "gzip", "br", or "zstd").
+         */
+        COMPRESSION_ALGORITHM,
+        /**
+         * Transport property key for number of bytes transferred. Value is a 
Long representing the total number of bytes transferred during the transport 
operation.
+         * This may be less than the content length in case of compression.
+         */
+        NUM_BYTES_TRANSFERRED;
+    }
+}
diff --git 
a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/RecordingTransportListener.java
 
b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/RecordingTransportListener.java
index 7dac9b666..5d87f19c5 100644
--- 
a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/RecordingTransportListener.java
+++ 
b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/RecordingTransportListener.java
@@ -21,8 +21,10 @@ package org.eclipse.aether.internal.test.util.http;
 import java.io.ByteArrayOutputStream;
 import java.nio.Buffer;
 import java.nio.ByteBuffer;
+import java.util.Map;
 
 import org.eclipse.aether.spi.connector.transport.TransportListener;
+import 
org.eclipse.aether.spi.connector.transport.TransportListener.TransportPropertyKey;
 import org.eclipse.aether.transfer.TransferCancelledException;
 
 public class RecordingTransportListener extends TransportListener {
@@ -42,7 +44,9 @@ public class RecordingTransportListener extends 
TransportListener {
     private boolean cancelProgress;
 
     @Override
-    public void transportStarted(long dataOffset, long dataLength) throws 
TransferCancelledException {
+    public void transportStarted(
+            long dataOffset, long dataLength, Map<TransportPropertyKey, 
String> transportProperties)
+            throws TransferCancelledException {
         startedCount++;
         progressedCount = 0;
         this.dataLength = dataLength;
diff --git 
a/maven-resolver-transport-apache/src/main/java/org/eclipse/aether/transport/apache/ApacheTransporter.java
 
b/maven-resolver-transport-apache/src/main/java/org/eclipse/aether/transport/apache/ApacheTransporter.java
index 3084c2ad7..a49a7587d 100644
--- 
a/maven-resolver-transport-apache/src/main/java/org/eclipse/aether/transport/apache/ApacheTransporter.java
+++ 
b/maven-resolver-transport-apache/src/main/java/org/eclipse/aether/transport/apache/ApacheTransporter.java
@@ -33,6 +33,7 @@ import java.nio.file.Path;
 import java.nio.file.StandardCopyOption;
 import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -92,6 +93,7 @@ import 
org.eclipse.aether.spi.connector.transport.AbstractTransporter;
 import org.eclipse.aether.spi.connector.transport.GetTask;
 import org.eclipse.aether.spi.connector.transport.PeekTask;
 import org.eclipse.aether.spi.connector.transport.PutTask;
+import org.eclipse.aether.spi.connector.transport.TransportListener;
 import org.eclipse.aether.spi.connector.transport.TransportTask;
 import org.eclipse.aether.spi.connector.transport.http.ChecksumExtractor;
 import org.eclipse.aether.spi.connector.transport.http.HttpTransporter;
@@ -674,11 +676,13 @@ final class ApacheTransporter extends AbstractTransporter 
implements HttpTranspo
                 }
             }
 
+            Map<TransportListener.TransportPropertyKey, String> 
transportProperties =
+                    createTransportProperties(response);
             final boolean resume = offset > 0L;
             final Path dataFile = task.getDataPath();
             if (dataFile == null) {
                 try (InputStream is = entity.getContent()) {
-                    utilGet(task, is, true, length, resume);
+                    utilGet(task, is, true, length, resume, 
transportProperties);
                     extractChecksums(response);
                 }
             } else {
@@ -690,7 +694,7 @@ final class ApacheTransporter extends AbstractTransporter 
implements HttpTranspo
                         }
                     }
                     try (InputStream is = entity.getContent()) {
-                        utilGet(task, is, true, length, resume);
+                        utilGet(task, is, true, length, resume, 
transportProperties);
                     }
                     tempFile.move();
                 } finally {
@@ -718,6 +722,15 @@ final class ApacheTransporter extends AbstractTransporter 
implements HttpTranspo
         }
     }
 
+    private static Map<TransportListener.TransportPropertyKey, String> 
createTransportProperties(
+            CloseableHttpResponse response) {
+        Map<TransportListener.TransportPropertyKey, String> properties = new 
HashMap<>();
+        properties.put(
+                HttpTransporter.HttpTransportPropertyKey.HTTP_VERSION,
+                response.getProtocolVersion().toString());
+        return properties;
+    }
+
     private static Function<String, String> headerGetter(CloseableHttpResponse 
closeableHttpResponse) {
         return s -> {
             Header header = closeableHttpResponse.getFirstHeader(s);
diff --git 
a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java
 
b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java
index f5b2b29a4..5b644f52a 100644
--- 
a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java
+++ 
b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java
@@ -77,6 +77,8 @@ import 
org.eclipse.aether.spi.connector.transport.AbstractTransporter;
 import org.eclipse.aether.spi.connector.transport.GetTask;
 import org.eclipse.aether.spi.connector.transport.PeekTask;
 import org.eclipse.aether.spi.connector.transport.PutTask;
+import org.eclipse.aether.spi.connector.transport.TransportListener;
+import 
org.eclipse.aether.spi.connector.transport.TransportListener.TransportPropertyKey;
 import org.eclipse.aether.spi.connector.transport.TransportTask;
 import org.eclipse.aether.spi.connector.transport.http.ChecksumExtractor;
 import org.eclipse.aether.spi.connector.transport.http.HttpTransporter;
@@ -370,9 +372,11 @@ final class JdkTransporter extends AbstractTransporter 
implements HttpTransporte
 
             final boolean downloadResumed = offset > 0L;
             final Path dataFile = task.getDataPath();
+            final Map<TransportListener.TransportPropertyKey, String> 
transportProperties =
+                    createTransportProperties(response);
             if (dataFile == null) {
                 try (InputStream is = response.body()) {
-                    utilGet(task, is, true, length, downloadResumed);
+                    utilGet(task, is, true, length, downloadResumed, 
transportProperties);
                 }
             } else {
                 try (PathProcessor.CollocatedTempFile tempFile = 
pathProcessor.newTempFile(dataFile)) {
@@ -383,7 +387,7 @@ final class JdkTransporter extends AbstractTransporter 
implements HttpTransporte
                         }
                     }
                     try (InputStream is = response.body()) {
-                        utilGet(task, is, true, length, downloadResumed);
+                        utilGet(task, is, true, length, downloadResumed, 
transportProperties);
                     }
                     tempFile.move();
                 } finally {
@@ -415,6 +419,21 @@ final class JdkTransporter extends AbstractTransporter 
implements HttpTransporte
         }
     }
 
+    private Map<TransportPropertyKey, String> 
createTransportProperties(HttpResponse<?> response) {
+        Map<TransportPropertyKey, String> props = new HashMap<>();
+        props.put(HttpTransportPropertyKey.HTTP_VERSION, 
response.version().toString());
+        response.sslSession().ifPresent(ssl -> {
+            props.put(
+                    HttpTransportPropertyKey.SSL_PROTOCOL,
+                    response.sslSession().get().getProtocol());
+            props.put(
+                    HttpTransportPropertyKey.SSL_CIPHER_SUITE,
+                    response.sslSession().get().getCipherSuite());
+        });
+        // TODO: add compression algorithm if any 
(https://github.com/mizosoft/methanol/issues/182)
+        return props;
+    }
+
     private static Function<String, String> headerGetter(HttpResponse<?> 
response) {
         return s -> response.headers().firstValue(s).orElse(null);
     }
diff --git 
a/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporter.java
 
b/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporter.java
index 3a90132a7..abf19a6e5 100644
--- 
a/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporter.java
+++ 
b/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporter.java
@@ -47,6 +47,8 @@ import 
org.eclipse.aether.spi.connector.transport.AbstractTransporter;
 import org.eclipse.aether.spi.connector.transport.GetTask;
 import org.eclipse.aether.spi.connector.transport.PeekTask;
 import org.eclipse.aether.spi.connector.transport.PutTask;
+import org.eclipse.aether.spi.connector.transport.TransportListener;
+import 
org.eclipse.aether.spi.connector.transport.TransportListener.TransportPropertyKey;
 import org.eclipse.aether.spi.connector.transport.TransportTask;
 import org.eclipse.aether.spi.connector.transport.http.ChecksumExtractor;
 import org.eclipse.aether.spi.connector.transport.http.HttpTransporter;
@@ -68,6 +70,7 @@ import org.eclipse.jetty.http.HttpHeader;
 import org.eclipse.jetty.http2.client.HTTP2Client;
 import 
org.eclipse.jetty.http2.client.transport.ClientConnectionFactoryOverHTTP2;
 import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.io.EndPoint.SslSessionData;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
 
 import static 
org.eclipse.aether.spi.connector.transport.http.HttpConstants.ACCEPT_ENCODING;
@@ -246,6 +249,7 @@ final class JettyTransporter extends AbstractTransporter 
implements HttpTranspor
         boolean resume = task.getResumeOffset() > 0L && task.getDataPath() != 
null;
         Response response;
         InputStreamResponseListener listener;
+        Map<TransportListener.TransportPropertyKey, String> 
transportProperties;
 
         while (true) {
             Request request = client.newRequest(resolve(task)).method("GET");
@@ -287,6 +291,7 @@ final class JettyTransporter extends AbstractTransporter 
implements HttpTranspor
                     throw new HttpTransporterException(statusCode);
                 });
             }
+            transportProperties = createTransportProperties(request, response);
             break;
         }
 
@@ -311,7 +316,7 @@ final class JettyTransporter extends AbstractTransporter 
implements HttpTranspor
         final Path dataFile = task.getDataPath();
         if (dataFile == null) {
             try (InputStream is = listener.getInputStream()) {
-                utilGet(task, is, true, length, downloadResumed);
+                utilGet(task, is, true, length, downloadResumed, 
transportProperties);
             }
         } else {
             try (PathProcessor.CollocatedTempFile tempFile = 
pathProcessor.newTempFile(dataFile)) {
@@ -322,7 +327,7 @@ final class JettyTransporter extends AbstractTransporter 
implements HttpTranspor
                     }
                 }
                 try (InputStream is = listener.getInputStream()) {
-                    utilGet(task, is, true, length, downloadResumed);
+                    utilGet(task, is, true, length, downloadResumed, 
transportProperties);
                 }
                 tempFile.move();
             } finally {
@@ -342,6 +347,21 @@ final class JettyTransporter extends AbstractTransporter 
implements HttpTranspor
         }
     }
 
+    private Map<TransportPropertyKey, String> 
createTransportProperties(Request request, Response response) {
+        SslSessionData sslSessionData = 
request.getConnection().getSslSessionData();
+        Map<TransportPropertyKey, String> properties = new HashMap<>();
+        properties.put(HttpTransportPropertyKey.HTTP_VERSION, 
response.getVersion().toString());
+        if (sslSessionData != null && sslSessionData.sslSession() != null) {
+            properties.put(
+                    HttpTransportPropertyKey.SSL_PROTOCOL,
+                    sslSessionData.sslSession().getProtocol());
+            properties.put(
+                    HttpTransportPropertyKey.SSL_CIPHER_SUITE,
+                    sslSessionData.sslSession().getCipherSuite());
+        }
+        return properties;
+    }
+
     private static Function<String, String> headerGetter(Response response) {
         return s -> response.getHeaders().get(s);
     }

Reply via email to