[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
This commit is contained in:
Tamas Cservenak 2022-12-01 13:53:08 +01:00 committed by GitHub
parent c6ecff9923
commit f70b0019cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 412 additions and 0 deletions

View File

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

View File

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

View File

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

View File

@ -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();
}
}

View File

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