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

cstamas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git


The following commit(s) were added to refs/heads/master by this push:
     new f70b0019c [MNG-7607] Add M4 Transport API (#884)
f70b0019c is described below

commit f70b0019ccad4915279dc782f05bd41afcc245e4
Author: Tamas Cservenak <ta...@cservenak.net>
AuthorDate: Thu Dec 1 13:53:08 2022 +0100

    [MNG-7607] Add M4 Transport API (#884)
    
    Something simple to use and would reuse all the auth/proxy etc data from 
Maven. Intentionally super-trivial API.
    
    If something more "serious" needed, plugin should probably roll it's own 
solution.
    
    ---
    
    https://issues.apache.org/jira/browse/MNG-7607
---
 .../org/apache/maven/api/services/Transport.java   | 115 +++++++++++++++
 .../maven/api/services/TransportProvider.java      |  49 +++++++
 .../api/services/TransportProviderException.java   |  33 +++++
 .../maven/internal/impl/DefaultTransport.java      | 154 +++++++++++++++++++++
 .../internal/impl/DefaultTransportProvider.java    |  61 ++++++++
 5 files changed, 412 insertions(+)

diff --git 
a/api/maven-api-core/src/main/java/org/apache/maven/api/services/Transport.java 
b/api/maven-api-core/src/main/java/org/apache/maven/api/services/Transport.java
new file mode 100644
index 000000000..3db9d8592
--- /dev/null
+++ 
b/api/maven-api-core/src/main/java/org/apache/maven/api/services/Transport.java
@@ -0,0 +1,115 @@
+/*
+ * 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.api.services;
+
+import java.io.Closeable;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.Optional;
+import org.apache.maven.api.RemoteRepository;
+import org.apache.maven.api.annotations.Consumer;
+import org.apache.maven.api.annotations.Experimental;
+import org.apache.maven.api.annotations.Nonnull;
+
+/**
+ * Transport for specified remote repository (using provided remote repository 
base URI as root). Must be treated as a
+ * resource, best in try-with-resource block.
+ *
+ * @since 4.0
+ */
+@Experimental
+@Consumer
+public interface Transport extends Closeable {
+    /**
+     * GETs the source URI content into target (does not have to exist, or 
will be overwritten if exist). The
+     * source MUST BE relative from the {@link RemoteRepository#getUrl()} root.
+     *
+     * @return {@code true} if operation succeeded, {@code false} if source 
does not exist.
+     * @throws RuntimeException If failed (and not due source not exists).
+     */
+    boolean get(@Nonnull URI relativeSource, @Nonnull Path target);
+
+    /**
+     * GETs the source URI content as byte array. The source MUST BE relative 
from the {@link RemoteRepository#getUrl()}
+     * root.
+     *
+     * @return the byte array if operation succeeded, {@code null} if source 
does not exist.
+     * @throws RuntimeException If failed (and not due source not exists).
+     */
+    @Nonnull
+    Optional<byte[]> getBytes(@Nonnull URI relativeSource);
+
+    /**
+     * GETs the source URI content as string. The source MUST BE relative from 
the {@link RemoteRepository#getUrl()}
+     * root.
+     *
+     * @return the string if operation succeeded, {@code null} if source does 
not exist.
+     * @throws RuntimeException If failed (and not due source not exists).
+     */
+    @Nonnull
+    Optional<String> getString(@Nonnull URI relativeSource, @Nonnull Charset 
charset);
+
+    /**
+     * GETs the source URI content as string using UTF8 charset. The source 
MUST BE relative from the
+     * {@link RemoteRepository#getUrl()} root.
+     *
+     * @return the string if operation succeeded, {@code null} if source does 
not exist.
+     * @throws RuntimeException If failed (and not due source not exists).
+     */
+    @Nonnull
+    default Optional<String> getString(@Nonnull URI relativeSource) {
+        return getString(relativeSource, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * PUTs the source file (must exist as file) to target URI. The target 
MUST BE relative from the
+     * {@link RemoteRepository#getUrl()} root.
+     *
+     * @throws RuntimeException If PUT fails for any reason.
+     */
+    void put(@Nonnull Path source, @Nonnull URI relativeTarget);
+
+    /**
+     * PUTs the source byte array to target URI. The target MUST BE relative 
from the
+     * {@link RemoteRepository#getUrl()} root.
+     *
+     * @throws RuntimeException If PUT fails for any reason.
+     */
+    void putBytes(@Nonnull byte[] source, @Nonnull URI relativeTarget);
+
+    /**
+     * PUTs the source string to target URI. The target MUST BE relative from 
the
+     * {@link RemoteRepository#getUrl()} root.
+     *
+     * @throws RuntimeException If PUT fails for any reason.
+     */
+    void putString(@Nonnull String source, @Nonnull Charset charset, @Nonnull 
URI relativeTarget);
+
+    /**
+     * PUTs the source string using UTF8 charset to target URI. The target 
MUST BE relative from the
+     * {@link RemoteRepository#getUrl()} root.
+     *
+     * @throws RuntimeException If PUT fails for any reason.
+     */
+    default void putString(@Nonnull String source, @Nonnull URI 
relativeTarget) {
+        putString(source, StandardCharsets.UTF_8, relativeTarget);
+    }
+}
diff --git 
a/api/maven-api-core/src/main/java/org/apache/maven/api/services/TransportProvider.java
 
b/api/maven-api-core/src/main/java/org/apache/maven/api/services/TransportProvider.java
new file mode 100644
index 000000000..c61f597fa
--- /dev/null
+++ 
b/api/maven-api-core/src/main/java/org/apache/maven/api/services/TransportProvider.java
@@ -0,0 +1,49 @@
+/*
+ * 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.api.services;
+
+import org.apache.maven.api.RemoteRepository;
+import org.apache.maven.api.Service;
+import org.apache.maven.api.Session;
+import org.apache.maven.api.annotations.Consumer;
+import org.apache.maven.api.annotations.Experimental;
+import org.apache.maven.api.annotations.Nonnull;
+
+/**
+ * Transporter provider is a service that provides somewhat trivial transport 
capabilities backed by Maven internals.
+ * This API does not try to cover all the requirements out there, just the 
basic ones, and is intentionally simple.
+ * If plugin or extension needs anything more complex feature wise (i.e. HTTP 
range support or alike) it should
+ * probably roll its own.
+ * <p>
+ * This implementation is backed by Maven Resolver API, supported protocols 
and transport selection depends on it. If
+ * resolver preference regarding transport is altered, it will affect this 
service as well.
+ *
+ * @since 4.0
+ */
+@Experimental
+@Consumer
+public interface TransportProvider extends Service {
+    /**
+     * Provides new {@link Transport} instance for given {@link 
RemoteRepository}, if possible.
+     *
+     * @throws TransportProviderException if passed in remote repository has 
invalid remote URL or unsupported protocol.
+     */
+    @Nonnull
+    Transport transport(@Nonnull Session session, @Nonnull RemoteRepository 
repository);
+}
diff --git 
a/api/maven-api-core/src/main/java/org/apache/maven/api/services/TransportProviderException.java
 
b/api/maven-api-core/src/main/java/org/apache/maven/api/services/TransportProviderException.java
new file mode 100644
index 000000000..4e7dff50c
--- /dev/null
+++ 
b/api/maven-api-core/src/main/java/org/apache/maven/api/services/TransportProviderException.java
@@ -0,0 +1,33 @@
+/*
+ * 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.api.services;
+
+import org.apache.maven.api.annotations.Consumer;
+import org.apache.maven.api.annotations.Experimental;
+
+/**
+ * @since 4.0
+ */
+@Experimental
+@Consumer
+public class TransportProviderException extends MavenException {
+    public TransportProviderException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git 
a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultTransport.java 
b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultTransport.java
new file mode 100644
index 000000000..033056bb2
--- /dev/null
+++ 
b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultTransport.java
@@ -0,0 +1,154 @@
+/*
+ * 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.internal.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Optional;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import org.apache.maven.api.services.Transport;
+import org.eclipse.aether.spi.connector.transport.GetTask;
+import org.eclipse.aether.spi.connector.transport.PutTask;
+import org.eclipse.aether.spi.connector.transport.Transporter;
+
+@Named
+@Singleton
+public class DefaultTransport implements Transport {
+    private final URI baseURI;
+    private final Transporter transporter;
+
+    public DefaultTransport(URI baseURI, Transporter transporter) {
+        this.baseURI = requireNonNull(baseURI);
+        this.transporter = requireNonNull(transporter);
+    }
+
+    @Override
+    public boolean get(URI relativeSource, Path target) {
+        requireNonNull(relativeSource, "relativeSource is null");
+        requireNonNull(target, "target is null");
+        if (relativeSource.isAbsolute()) {
+            throw new IllegalArgumentException("Supplied URI is not relative");
+        }
+        URI source = baseURI.resolve(relativeSource);
+        if (!source.toASCIIString().startsWith(baseURI.toASCIIString())) {
+            throw new IllegalArgumentException("Supplied relative URI escapes 
baseUrl");
+        }
+        GetTask getTask = new GetTask(source);
+        getTask.setDataFile(target.toFile());
+        try {
+            transporter.get(getTask);
+            return true;
+        } catch (Exception e) {
+            if (Transporter.ERROR_NOT_FOUND != transporter.classify(e)) {
+                throw new RuntimeException(e);
+            }
+            return false;
+        }
+    }
+
+    @Override
+    public Optional<byte[]> getBytes(URI relativeSource) {
+        try {
+            Path tempPath = null;
+            try {
+                tempPath = Files.createTempFile("transport-get", "tmp");
+                if (get(relativeSource, tempPath)) {
+                    // TODO: check file size and prevent OOM?
+                    return Optional.of(Files.readAllBytes(tempPath));
+                }
+                return Optional.empty();
+            } finally {
+                if (tempPath != null) {
+                    Files.deleteIfExists(tempPath);
+                }
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    @Override
+    public Optional<String> getString(URI relativeSource, Charset charset) {
+        requireNonNull(charset, "charset is null");
+        Optional<byte[]> data = getBytes(relativeSource);
+        return data.map(bytes -> new String(bytes, charset));
+    }
+
+    @Override
+    public void put(Path source, URI relativeTarget) {
+        requireNonNull(source, "source is null");
+        requireNonNull(relativeTarget, "relativeTarget is null");
+        if (Files.isRegularFile(source)) {
+            throw new IllegalArgumentException("source file does not exist or 
is not a file");
+        }
+        if (relativeTarget.isAbsolute()) {
+            throw new IllegalArgumentException("Supplied URI is not relative");
+        }
+        URI target = baseURI.resolve(relativeTarget);
+        if (!target.toASCIIString().startsWith(baseURI.toASCIIString())) {
+            throw new IllegalArgumentException("Supplied relative URI escapes 
baseUrl");
+        }
+
+        PutTask putTask = new PutTask(target);
+        putTask.setDataFile(source.toFile());
+        try {
+            transporter.put(putTask);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void putBytes(byte[] source, URI relativeTarget) {
+        requireNonNull(source, "source is null");
+        try {
+            Path tempPath = null;
+            try {
+                tempPath = Files.createTempFile("transport-get", "tmp");
+                Files.write(tempPath, source);
+                put(tempPath, relativeTarget);
+            } finally {
+                if (tempPath != null) {
+                    Files.deleteIfExists(tempPath);
+                }
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    @Override
+    public void putString(String source, Charset charset, URI relativeTarget) {
+        requireNonNull(source, "source string is null");
+        requireNonNull(charset, "charset is null");
+        putBytes(source.getBytes(charset), relativeTarget);
+    }
+
+    @Override
+    public void close() {
+        transporter.close();
+    }
+}
diff --git 
a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultTransportProvider.java
 
b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultTransportProvider.java
new file mode 100644
index 000000000..4afec0f44
--- /dev/null
+++ 
b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultTransportProvider.java
@@ -0,0 +1,61 @@
+/*
+ * 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.internal.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import org.apache.maven.api.RemoteRepository;
+import org.apache.maven.api.Session;
+import org.apache.maven.api.services.Transport;
+import org.apache.maven.api.services.TransportProvider;
+import org.apache.maven.api.services.TransportProviderException;
+import org.eclipse.aether.spi.connector.transport.TransporterProvider;
+import org.eclipse.aether.transfer.NoTransporterException;
+
+@Named
+@Singleton
+public class DefaultTransportProvider implements TransportProvider {
+    private final 
org.eclipse.aether.spi.connector.transport.TransporterProvider 
transporterProvider;
+
+    @Inject
+    public DefaultTransportProvider(TransporterProvider transporterProvider) {
+        this.transporterProvider = requireNonNull(transporterProvider);
+    }
+
+    @Override
+    public Transport transport(Session session, RemoteRepository repository) {
+        try {
+            URI baseURI = new URI(repository.getUrl());
+            return new DefaultTransport(
+                    baseURI,
+                    transporterProvider.newTransporter(
+                            ((DefaultSession) session).getSession(),
+                            ((DefaultRemoteRepository) 
repository).getRepository()));
+        } catch (URISyntaxException e) {
+            throw new TransportProviderException("Remote repository URL 
invalid", e);
+        } catch (NoTransporterException e) {
+            throw new TransportProviderException("Unsupported remote 
repository", e);
+        }
+    }
+}

Reply via email to