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); + } + } +}