This is an automated email from the ASF dual-hosted git repository. rfscholte pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/maven-resolver.git
The following commit(s) were added to refs/heads/master by this push: new cff6c4f [MRESOLVER-46] Add support InputStream/OutputStream transformers cff6c4f is described below commit cff6c4f27c46904831e786a7a2e673d971975ab1 Author: rfscholte <rfscho...@apache.org> AuthorDate: Fri Sep 28 17:31:44 2018 +0200 [MRESOLVER-46] Add support InputStream/OutputStream transformers --- .../AbstractForwardingRepositorySystemSession.java | 32 +++++++ .../aether/DefaultRepositorySystemSession.java | 36 ++++++++ .../eclipse/aether/RepositorySystemSession.java | 8 ++ .../eclipse/aether/transform/FileTransformer.java | 51 +++++++++++ .../aether/transform/FileTransformerManager.java | 49 +++++++++++ .../connector/basic/BasicRepositoryConnector.java | 74 ++++++++++++++-- .../aether/internal/impl/DefaultDeployer.java | 28 ++++++- .../aether/internal/impl/DefaultInstaller.java | 61 +++++++++++--- .../aether/internal/impl/DefaultDeployerTest.java | 42 +++++++++- .../aether/internal/impl/DefaultInstallerTest.java | 58 ++++++++++++- .../internal/impl/StubFileTransformerManager.java | 50 +++++++++++ .../aether/spi/connector/ArtifactUpload.java | 41 ++++++++- .../aether/transport/http/HttpTransporterTest.java | 2 +- .../org/eclipse/aether/util/ChecksumUtils.java | 55 ++++-------- .../org/eclipse/aether/util/ChecksumUtilTest.java | 98 +++++++++++++++------- 15 files changed, 589 insertions(+), 96 deletions(-) diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/AbstractForwardingRepositorySystemSession.java b/maven-resolver-api/src/main/java/org/eclipse/aether/AbstractForwardingRepositorySystemSession.java index 20df431..35759cb 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/AbstractForwardingRepositorySystemSession.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/AbstractForwardingRepositorySystemSession.java @@ -36,6 +36,7 @@ import org.eclipse.aether.repository.WorkspaceReader; import org.eclipse.aether.resolution.ArtifactDescriptorPolicy; import org.eclipse.aether.resolution.ResolutionErrorPolicy; import org.eclipse.aether.transfer.TransferListener; +import org.eclipse.aether.transform.FileTransformerManager; /** * A special repository system session to enable decorating or proxying another session. To do so, clients have to @@ -61,129 +62,160 @@ public abstract class AbstractForwardingRepositorySystemSession */ protected abstract RepositorySystemSession getSession(); + @Override public boolean isOffline() { return getSession().isOffline(); } + @Override public boolean isIgnoreArtifactDescriptorRepositories() { return getSession().isIgnoreArtifactDescriptorRepositories(); } + @Override public ResolutionErrorPolicy getResolutionErrorPolicy() { return getSession().getResolutionErrorPolicy(); } + @Override public ArtifactDescriptorPolicy getArtifactDescriptorPolicy() { return getSession().getArtifactDescriptorPolicy(); } + @Override public String getChecksumPolicy() { return getSession().getChecksumPolicy(); } + @Override public String getUpdatePolicy() { return getSession().getUpdatePolicy(); } + @Override public LocalRepository getLocalRepository() { return getSession().getLocalRepository(); } + @Override public LocalRepositoryManager getLocalRepositoryManager() { return getSession().getLocalRepositoryManager(); } + @Override public WorkspaceReader getWorkspaceReader() { return getSession().getWorkspaceReader(); } + @Override public RepositoryListener getRepositoryListener() { return getSession().getRepositoryListener(); } + @Override public TransferListener getTransferListener() { return getSession().getTransferListener(); } + @Override public Map<String, String> getSystemProperties() { return getSession().getSystemProperties(); } + @Override public Map<String, String> getUserProperties() { return getSession().getUserProperties(); } + @Override public Map<String, Object> getConfigProperties() { return getSession().getConfigProperties(); } + @Override public MirrorSelector getMirrorSelector() { return getSession().getMirrorSelector(); } + @Override public ProxySelector getProxySelector() { return getSession().getProxySelector(); } + @Override public AuthenticationSelector getAuthenticationSelector() { return getSession().getAuthenticationSelector(); } + @Override public ArtifactTypeRegistry getArtifactTypeRegistry() { return getSession().getArtifactTypeRegistry(); } + @Override public DependencyTraverser getDependencyTraverser() { return getSession().getDependencyTraverser(); } + @Override public DependencyManager getDependencyManager() { return getSession().getDependencyManager(); } + @Override public DependencySelector getDependencySelector() { return getSession().getDependencySelector(); } + @Override public VersionFilter getVersionFilter() { return getSession().getVersionFilter(); } + @Override public DependencyGraphTransformer getDependencyGraphTransformer() { return getSession().getDependencyGraphTransformer(); } + @Override public SessionData getData() { return getSession().getData(); } + @Override public RepositoryCache getCache() { return getSession().getCache(); } + + @Override + public FileTransformerManager geFileTransformerManager() + { + return getSession().geFileTransformerManager(); + } } diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/DefaultRepositorySystemSession.java b/maven-resolver-api/src/main/java/org/eclipse/aether/DefaultRepositorySystemSession.java index 13773df..8ba4e15 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/DefaultRepositorySystemSession.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/DefaultRepositorySystemSession.java @@ -24,6 +24,9 @@ import java.util.HashMap; import java.util.Map; import static java.util.Objects.requireNonNull; +import java.util.Collection; + +import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.artifact.ArtifactType; import org.eclipse.aether.artifact.ArtifactTypeRegistry; import org.eclipse.aether.collection.DependencyGraphTransformer; @@ -44,6 +47,8 @@ import org.eclipse.aether.repository.WorkspaceReader; import org.eclipse.aether.resolution.ArtifactDescriptorPolicy; import org.eclipse.aether.resolution.ResolutionErrorPolicy; import org.eclipse.aether.transfer.TransferListener; +import org.eclipse.aether.transform.FileTransformer; +import org.eclipse.aether.transform.FileTransformerManager; /** * A simple repository system session. @@ -73,6 +78,8 @@ public final class DefaultRepositorySystemSession private LocalRepositoryManager localRepositoryManager; + private FileTransformerManager fileTransformerManager; + private WorkspaceReader workspaceReader; private RepositoryListener repositoryListener; @@ -130,6 +137,7 @@ public final class DefaultRepositorySystemSession proxySelector = NullProxySelector.INSTANCE; authenticationSelector = NullAuthenticationSelector.INSTANCE; artifactTypeRegistry = NullArtifactTypeRegistry.INSTANCE; + fileTransformerManager = NullFileTransformerManager.INSTANCE; data = new DefaultSessionData(); } @@ -317,6 +325,23 @@ public final class DefaultRepositorySystemSession return this; } + @Override + public FileTransformerManager geFileTransformerManager() + { + return fileTransformerManager; + } + + public DefaultRepositorySystemSession setFileTransformerManager( FileTransformerManager fileTransformerManager ) + { + failIfReadOnly(); + this.fileTransformerManager = fileTransformerManager; + if ( this.fileTransformerManager == null ) + { + this.fileTransformerManager = NullFileTransformerManager.INSTANCE; + } + return this; + } + public WorkspaceReader getWorkspaceReader() { return workspaceReader; @@ -829,4 +854,15 @@ public final class DefaultRepositorySystemSession } + static final class NullFileTransformerManager implements FileTransformerManager + { + public static final FileTransformerManager INSTANCE = new NullFileTransformerManager(); + + @Override + public Collection<FileTransformer> getTransformersForArtifact( Artifact artifact ) + { + return Collections.emptyList(); + } + } + } diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystemSession.java b/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystemSession.java index 888f29c..025ec80 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystemSession.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystemSession.java @@ -37,6 +37,7 @@ import org.eclipse.aether.repository.WorkspaceReader; import org.eclipse.aether.resolution.ArtifactDescriptorPolicy; import org.eclipse.aether.resolution.ResolutionErrorPolicy; import org.eclipse.aether.transfer.TransferListener; +import org.eclipse.aether.transform.FileTransformerManager; /** * Defines settings and components that control the repository system. Once initialized, the session object itself is @@ -260,4 +261,11 @@ public interface RepositorySystemSession */ RepositoryCache getCache(); + /** + * Get the file transformer manager + * + * @return the manager, never {@code null} + */ + FileTransformerManager geFileTransformerManager(); + } diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/transform/FileTransformer.java b/maven-resolver-api/src/main/java/org/eclipse/aether/transform/FileTransformer.java new file mode 100644 index 0000000..17c997c --- /dev/null +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/transform/FileTransformer.java @@ -0,0 +1,51 @@ +package org.eclipse.aether.transform; + +/* + * 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. + */ + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +import org.eclipse.aether.artifact.Artifact; + +/** + * Can transform a file while installing/deploying + * + * @author Robert Scholte + * @since 1.2.0 + */ +public interface FileTransformer +{ + /** + * Transform the target location + * + * @param artifact the original artifact + * @return the transformed artifact + */ + Artifact transformArtifact( Artifact artifact ); + + /** + * Transform the data + * + * @param file the file with the original data + * @return the transformed data + */ + InputStream transformData( File file ) throws IOException; +} diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/transform/FileTransformerManager.java b/maven-resolver-api/src/main/java/org/eclipse/aether/transform/FileTransformerManager.java new file mode 100644 index 0000000..9eaec01 --- /dev/null +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/transform/FileTransformerManager.java @@ -0,0 +1,49 @@ +package org.eclipse.aether.transform; + +/* + * 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. + */ + +import java.util.Collection; + +import org.eclipse.aether.artifact.Artifact; + +/** + * Manager the FileTransformers + * + * @author Robert Scholte + * @since 1.2.0 + */ +public interface FileTransformerManager +{ + /** + * <p> + * All transformers for this specific artifact. Be aware that if you want to create additional files, but also want + * to the original to be deployed, you must add an explicit transformer for that file too (one that doesn't + * transform the artifact and data). + * </p> + * + * <p><strong>IMPORTANT</strong> When using a fileTransformer, the content of the file is stored in memory to ensure + * that file content and checksums stay in sync! + * </p> + * + * @param artifact the artifact + * @return a collection of FileTransformers to apply on the artifact, never {@code null} + */ + Collection<FileTransformer> getTransformersForArtifact( Artifact artifact ); +} diff --git a/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/BasicRepositoryConnector.java b/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/BasicRepositoryConnector.java index 87c8191..d15f80c 100644 --- a/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/BasicRepositoryConnector.java +++ b/maven-resolver-connector-basic/src/main/java/org/eclipse/aether/connector/basic/BasicRepositoryConnector.java @@ -19,15 +19,18 @@ package org.eclipse.aether.connector.basic; * under the License. */ +import static java.util.Objects.requireNonNull; + +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.net.URI; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; -import static java.util.Objects.requireNonNull; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; @@ -60,6 +63,7 @@ import org.eclipse.aether.transfer.NoRepositoryLayoutException; import org.eclipse.aether.transfer.NoTransporterException; import org.eclipse.aether.transfer.TransferEvent; import org.eclipse.aether.transfer.TransferResource; +import org.eclipse.aether.transform.FileTransformer; import org.eclipse.aether.util.ChecksumUtils; import org.eclipse.aether.util.ConfigUtils; import org.eclipse.aether.util.concurrency.RunnableErrorForwarder; @@ -279,7 +283,7 @@ final class BasicRepositoryConnector List<RepositoryLayout.Checksum> checksums = layout.getChecksums( transfer.getArtifact(), true, location ); - Runnable task = new PutTaskRunner( location, transfer.getFile(), checksums, listener ); + Runnable task = new PutTaskRunner( location, transfer.getFile(), transfer.getFileTransformer(), checksums, listener ); task.run(); } @@ -495,24 +499,70 @@ final class BasicRepositoryConnector private final File file; + private final FileTransformer fileTransformer; + private final Collection<RepositoryLayout.Checksum> checksums; PutTaskRunner( URI path, File file, List<RepositoryLayout.Checksum> checksums, + TransferTransportListener<?> listener ) + { + this( path, file, null, checksums, listener ); + } + + /** + * <strong>IMPORTANT</strong> When using a fileTransformer, the content of the file is stored in memory to + * ensure that file content and checksums stay in sync! + * + * @param path + * @param file + * @param fileTransformer + * @param checksums + * @param listener + */ + PutTaskRunner( URI path, File file, FileTransformer fileTransformer, List<RepositoryLayout.Checksum> checksums, TransferTransportListener<?> listener ) { super( path, listener ); this.file = requireNonNull( file, "source file cannot be null" ); + this.fileTransformer = fileTransformer; this.checksums = safe( checksums ); } protected void runTask() throws Exception { - transporter.put( new PutTask( path ).setDataFile( file ).setListener( listener ) ); - uploadChecksums( file, path ); + if ( fileTransformer != null ) + { + // transform data once to byte array, ensure constant data for checksum + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + + try ( InputStream transformData = fileTransformer.transformData( file ) ) + { + for ( int read; ( read = transformData.read( buffer, 0, buffer.length ) ) != -1; ) + { + baos.write( buffer, 0, read ); + } + } + + byte[] bytes = baos.toByteArray(); + transporter.put( new PutTask( path ).setDataBytes( bytes ).setListener( listener ) ); + uploadChecksums( file, bytes, path ); + } + else + { + transporter.put( new PutTask( path ).setDataFile( file ).setListener( listener ) ); + uploadChecksums( file, null , path ); + } } - private void uploadChecksums( File file, URI location ) + /** + * + * @param file source + * @param bytes transformed data from file or {@code null} + * @param location target + */ + private void uploadChecksums( File file, byte[] bytes, URI location ) { if ( checksums.isEmpty() ) { @@ -520,12 +570,22 @@ final class BasicRepositoryConnector } try { - Set<String> algos = new HashSet<String>(); + Set<String> algos = new HashSet<>(); for ( RepositoryLayout.Checksum checksum : checksums ) { algos.add( checksum.getAlgorithm() ); } - Map<String, Object> sumsByAlgo = ChecksumUtils.calc( file, algos ); + + Map<String, Object> sumsByAlgo; + if ( bytes != null ) + { + sumsByAlgo = ChecksumUtils.calc( bytes, algos ); + } + else + { + sumsByAlgo = ChecksumUtils.calc( file, algos ); + } + for ( RepositoryLayout.Checksum checksum : checksums ) { uploadChecksum( checksum.getLocation(), sumsByAlgo.get( checksum.getAlgorithm() ) ); diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultDeployer.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultDeployer.java index 194ce30..4d6f5be 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultDeployer.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultDeployer.java @@ -71,6 +71,8 @@ import org.eclipse.aether.transfer.NoRepositoryConnectorException; import org.eclipse.aether.transfer.RepositoryOfflineException; import org.eclipse.aether.transfer.TransferCancelledException; import org.eclipse.aether.transfer.TransferEvent; +import org.eclipse.aether.transform.FileTransformer; +import org.eclipse.aether.transform.FileTransformerManager; /** */ @@ -237,6 +239,8 @@ public class DefaultDeployer { List<? extends MetadataGenerator> generators = getMetadataGenerators( session, request ); + FileTransformerManager fileTransformerManager = session.geFileTransformerManager(); + List<ArtifactUpload> artifactUploads = new ArrayList<ArtifactUpload>(); List<MetadataUpload> metadataUploads = new ArrayList<MetadataUpload>(); IdentityHashMap<Metadata, Object> processedMetadata = new IdentityHashMap<Metadata, Object>(); @@ -266,10 +270,26 @@ public class DefaultDeployer artifacts.set( i, artifact ); - ArtifactUpload upload = new ArtifactUpload( artifact, artifact.getFile() ); - upload.setTrace( trace ); - upload.setListener( new ArtifactUploadListener( catapult, upload ) ); - artifactUploads.add( upload ); + Collection<FileTransformer> fileTransformers = fileTransformerManager.getTransformersForArtifact( artifact ); + if ( !fileTransformers.isEmpty() ) + { + for ( FileTransformer fileTransformer : fileTransformers ) + { + Artifact targetArtifact = fileTransformer.transformArtifact( artifact ); + + ArtifactUpload upload = new ArtifactUpload( targetArtifact, artifact.getFile(), fileTransformer ); + upload.setTrace( trace ); + upload.setListener( new ArtifactUploadListener( catapult, upload ) ); + artifactUploads.add( upload ); + } + } + else + { + ArtifactUpload upload = new ArtifactUpload( artifact, artifact.getFile() ); + upload.setTrace( trace ); + upload.setListener( new ArtifactUploadListener( catapult, upload ) ); + artifactUploads.add( upload ); + } } connector.put( artifactUploads, null ); diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultInstaller.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultInstaller.java index 7cd14d0..e4c998a 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultInstaller.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultInstaller.java @@ -19,12 +19,14 @@ package org.eclipse.aether.internal.impl; * under the License. */ +import static java.util.Objects.requireNonNull; + import java.io.File; +import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.IdentityHashMap; import java.util.List; -import static java.util.Objects.requireNonNull; import java.util.Set; import javax.inject.Inject; @@ -52,6 +54,7 @@ import org.eclipse.aether.repository.LocalRepositoryManager; import org.eclipse.aether.spi.io.FileProcessor; import org.eclipse.aether.spi.locator.Service; import org.eclipse.aether.spi.locator.ServiceLocator; +import org.eclipse.aether.transform.FileTransformer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -236,9 +239,37 @@ public class DefaultInstaller File srcFile = artifact.getFile(); - File dstFile = new File( lrm.getRepository().getBasedir(), lrm.getPathForLocalArtifact( artifact ) ); + Collection<FileTransformer> fileTransformers = session.geFileTransformerManager().getTransformersForArtifact( artifact ); + if ( fileTransformers.isEmpty() ) + { + install( session, trace, artifact, lrm, srcFile, null ); + } + else + { + for ( FileTransformer fileTransformer : fileTransformers ) + { + install( session, trace, artifact, lrm, srcFile, fileTransformer ); + } + } + } + + private void install( RepositorySystemSession session, RequestTrace trace, Artifact artifact, + LocalRepositoryManager lrm, File srcFile, FileTransformer fileTransformer ) + throws InstallationException + { + final Artifact targetArtifact; + if ( fileTransformer != null ) + { + targetArtifact = fileTransformer.transformArtifact( artifact ); + } + else + { + targetArtifact = artifact; + } + + File dstFile = new File( lrm.getRepository().getBasedir(), lrm.getPathForLocalArtifact( targetArtifact ) ); - artifactInstalling( session, trace, artifact, dstFile ); + artifactInstalling( session, trace, targetArtifact, dstFile ); Exception exception = null; try @@ -249,29 +280,37 @@ public class DefaultInstaller } boolean copy = - "pom".equals( artifact.getExtension() ) || srcFile.lastModified() != dstFile.lastModified() + "pom".equals( targetArtifact.getExtension() ) || srcFile.lastModified() != dstFile.lastModified() || srcFile.length() != dstFile.length() || !srcFile.exists(); - if ( copy ) + if ( !copy ) { - fileProcessor.copy( srcFile, dstFile ); - dstFile.setLastModified( srcFile.lastModified() ); + LOGGER.debug( "Skipped re-installing {} to {}, seems unchanged", srcFile, dstFile ); + } + else if ( fileTransformer != null ) + { + try ( InputStream is = fileTransformer.transformData( srcFile ) ) + { + fileProcessor.write( dstFile, is ); + dstFile.setLastModified( srcFile.lastModified() ); + } } else { - LOGGER.debug( "Skipped re-installing {} to {}, seems unchanged", srcFile, dstFile ); + fileProcessor.copy( srcFile, dstFile ); + dstFile.setLastModified( srcFile.lastModified() ); } - lrm.add( session, new LocalArtifactRegistration( artifact ) ); + lrm.add( session, new LocalArtifactRegistration( targetArtifact ) ); } catch ( Exception e ) { exception = e; - throw new InstallationException( "Failed to install artifact " + artifact + ": " + e.getMessage(), e ); + throw new InstallationException( "Failed to install artifact " + targetArtifact + ": " + e.getMessage(), e ); } finally { - artifactInstalled( session, trace, artifact, dstFile, exception ); + artifactInstalled( session, trace, targetArtifact, dstFile, exception ); } } diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultDeployerTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultDeployerTest.java index 9465e87..fa7dc42 100644 --- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultDeployerTest.java +++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultDeployerTest.java @@ -19,10 +19,16 @@ package org.eclipse.aether.internal.impl; * under the License. */ -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -37,7 +43,6 @@ import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.deployment.DeployRequest; import org.eclipse.aether.deployment.DeploymentException; -import org.eclipse.aether.internal.impl.DefaultDeployer; import org.eclipse.aether.internal.test.util.TestFileProcessor; import org.eclipse.aether.internal.test.util.TestFileUtils; import org.eclipse.aether.internal.test.util.TestUtils; @@ -52,6 +57,8 @@ import org.eclipse.aether.spi.connector.MetadataDownload; import org.eclipse.aether.spi.connector.MetadataUpload; import org.eclipse.aether.spi.connector.RepositoryConnector; import org.eclipse.aether.transfer.MetadataNotFoundException; +import org.eclipse.aether.transform.FileTransformer; +import org.eclipse.aether.util.artifact.SubArtifact; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -382,4 +389,35 @@ public class DefaultDeployerTest assertNull( props.toString(), props.get( "old" ) ); } + @Test + public void testFileTransformer() throws Exception + { + final Artifact transformedArtifact = new SubArtifact( artifact, null, "raj" ); + FileTransformer transformer = new FileTransformer() + { + @Override + public InputStream transformData( File file ) + { + return new ByteArrayInputStream( "transformed data".getBytes( StandardCharsets.UTF_8 ) ); + } + + @Override + public Artifact transformArtifact( Artifact artifact ) + { + return transformedArtifact; + } + }; + + StubFileTransformerManager fileTransformerManager = new StubFileTransformerManager(); + fileTransformerManager.addFileTransformer( "jar", transformer ); + session.setFileTransformerManager( fileTransformerManager ); + + request = new DeployRequest(); + request.addArtifact( artifact ); + deployer.deploy( session, request ); + + Artifact putArtifact = connector.getActualArtifactPutRequests().get( 0 ); + assertEquals( transformedArtifact, putArtifact ); + } + } diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultInstallerTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultInstallerTest.java index efabedd..68a3547 100644 --- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultInstallerTest.java +++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultInstallerTest.java @@ -19,10 +19,20 @@ package org.eclipse.aether.internal.impl; * under the License. */ -import static org.junit.Assert.*; - +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; import java.io.File; +import java.io.FileReader; import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.List; import org.eclipse.aether.DefaultRepositorySystemSession; @@ -33,8 +43,6 @@ import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.installation.InstallRequest; import org.eclipse.aether.installation.InstallResult; import org.eclipse.aether.installation.InstallationException; -import org.eclipse.aether.internal.impl.DefaultFileProcessor; -import org.eclipse.aether.internal.impl.DefaultInstaller; import org.eclipse.aether.internal.test.util.TestFileProcessor; import org.eclipse.aether.internal.test.util.TestFileUtils; import org.eclipse.aether.internal.test.util.TestLocalRepositoryManager; @@ -42,6 +50,8 @@ import org.eclipse.aether.internal.test.util.TestUtils; import org.eclipse.aether.metadata.DefaultMetadata; import org.eclipse.aether.metadata.Metadata; import org.eclipse.aether.metadata.Metadata.Nature; +import org.eclipse.aether.transform.FileTransformer; +import org.eclipse.aether.util.artifact.SubArtifact; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -410,4 +420,44 @@ public class DefaultInstallerTest assertEquals( "artifact timestamp was not set to src file", artifact.getFile().lastModified(), localArtifactFile.lastModified() ); } + + @Test + public void testFileTransformer() throws Exception + { + final Artifact transformedArtifact = new SubArtifact( artifact, null, "raj" ); + FileTransformer transformer = new FileTransformer() + { + @Override + public InputStream transformData( File file ) + throws IOException + { + return new ByteArrayInputStream( "transformed data".getBytes( StandardCharsets.UTF_8 ) ); + } + + @Override + public Artifact transformArtifact( Artifact artifact ) + { + return transformedArtifact; + } + }; + + StubFileTransformerManager fileTransformerManager = new StubFileTransformerManager(); + fileTransformerManager.addFileTransformer( "jar", transformer ); + session.setFileTransformerManager( fileTransformerManager ); + + request = new InstallRequest(); + request.addArtifact( artifact ); + installer.install( session, request ); + + assertFalse( localArtifactFile.exists() ); + + String transformedArtifactPath = session.getLocalRepositoryManager().getPathForLocalArtifact( transformedArtifact ); + File transformedArtifactFile = new File( session.getLocalRepository().getBasedir(), transformedArtifactPath ); + assertTrue( transformedArtifactFile.exists() ); + + try ( BufferedReader r = new BufferedReader( new FileReader( transformedArtifactFile ) ) ) + { + assertEquals( "transformed data", r.readLine() ); + } + } } diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/StubFileTransformerManager.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/StubFileTransformerManager.java new file mode 100644 index 0000000..a079a2a --- /dev/null +++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/StubFileTransformerManager.java @@ -0,0 +1,50 @@ +package org.eclipse.aether.internal.impl; + +/* + * 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. + */ + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.transform.FileTransformer; +import org.eclipse.aether.transform.FileTransformerManager; + +public class StubFileTransformerManager implements FileTransformerManager +{ + private Map<String, Collection<FileTransformer>> fileTransformers = new HashMap<>(); + + @Override + public Collection<FileTransformer> getTransformersForArtifact( Artifact artifact ) + { + return fileTransformers.get( artifact.getExtension() ); + } + + public void addFileTransformer( String extension, FileTransformer fileTransformer ) + { + if ( !fileTransformers.containsKey( extension ) ) + { + fileTransformers.put( extension, new HashSet<FileTransformer>() ); + } + fileTransformers.get( extension ).add( fileTransformer ); + } + +} diff --git a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/ArtifactUpload.java b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/ArtifactUpload.java index f85539e..90323b1 100644 --- a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/ArtifactUpload.java +++ b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/ArtifactUpload.java @@ -25,6 +25,7 @@ import org.eclipse.aether.RequestTrace; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.transfer.ArtifactTransferException; import org.eclipse.aether.transfer.TransferListener; +import org.eclipse.aether.transform.FileTransformer; /** * An upload of an artifact to a remote repository. A repository connector processing this upload has to use @@ -33,6 +34,7 @@ import org.eclipse.aether.transfer.TransferListener; public final class ArtifactUpload extends ArtifactTransfer { + private FileTransformer fileTransformer; /** * Creates a new uninitialized upload. @@ -54,6 +56,24 @@ public final class ArtifactUpload setFile( file ); } + /** + * <p>Creates a new upload with the specified properties.</p> + * + * <p><strong>IMPORTANT</strong> When using a fileTransformer, the + * content of the file is stored in memory to ensure that file content and checksums stay in sync! + * </p> + * + * @param artifact The artifact to upload, may be {@code null}. + * @param file The local file to upload the artifact from, may be {@code null}. + * @param fileTransformer The file transformer, may be {@code null}. + */ + public ArtifactUpload( Artifact artifact, File file, FileTransformer fileTransformer ) + { + setArtifact( artifact ); + setFile( file ); + setFileTransformer( fileTransformer ); + } + @Override public ArtifactUpload setArtifact( Artifact artifact ) { @@ -88,11 +108,30 @@ public final class ArtifactUpload super.setTrace( trace ); return this; } + + public ArtifactUpload setFileTransformer( FileTransformer fileTransformer ) + { + this.fileTransformer = fileTransformer; + return this; + } + + public FileTransformer getFileTransformer() + { + return fileTransformer; + } @Override public String toString() { - return getArtifact() + " - " + getFile(); + if ( getFileTransformer() != null ) + { + return getArtifact() + " >>> " + getFileTransformer().transformArtifact( getArtifact() ) + + " - " + getFile(); + } + else + { + return getArtifact() + " - " + getFile(); + } } } diff --git a/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpTransporterTest.java b/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpTransporterTest.java index 880f2d8..384827f 100644 --- a/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpTransporterTest.java +++ b/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpTransporterTest.java @@ -921,7 +921,7 @@ public class HttpTransporterTest assertEquals( 1, listener.startedCount ); } - @Test( timeout = 10000L ) + @Test( timeout = 20000L ) public void testConcurrency() throws Exception { diff --git a/maven-resolver-util/src/main/java/org/eclipse/aether/util/ChecksumUtils.java b/maven-resolver-util/src/main/java/org/eclipse/aether/util/ChecksumUtils.java index 415e712..9b4714c 100644 --- a/maven-resolver-util/src/main/java/org/eclipse/aether/util/ChecksumUtils.java +++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/ChecksumUtils.java @@ -20,6 +20,7 @@ package org.eclipse.aether.util; */ import java.io.BufferedReader; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -54,10 +55,8 @@ public final class ChecksumUtils throws IOException { String checksum = ""; - BufferedReader br = null; - try + try ( BufferedReader br = new BufferedReader( new InputStreamReader( new FileInputStream( checksumFile ), StandardCharsets.UTF_8 ), 512 ) ) { - br = new BufferedReader( new InputStreamReader( new FileInputStream( checksumFile ), StandardCharsets.UTF_8 ), 512 ); while ( true ) { String line = br.readLine(); @@ -73,21 +72,6 @@ public final class ChecksumUtils } } } - finally - { - try - { - if ( br != null ) - { - br.close(); - br = null; - } - } - catch ( IOException e ) - { - // Suppressed due to an exception already thrown in the try block. - } - } if ( checksum.matches( ".+= [0-9A-Fa-f]+" ) ) { @@ -118,6 +102,20 @@ public final class ChecksumUtils * @throws IOException If the data file could not be read. */ public static Map<String, Object> calc( File dataFile, Collection<String> algos ) + throws IOException + { + return calc( new FileInputStream( dataFile ), algos ); + } + + + public static Map<String, Object> calc( byte[] dataBytes, Collection<String> algos ) + throws IOException + { + return calc( new ByteArrayInputStream( dataBytes ), algos ); + } + + + private static Map<String, Object> calc( InputStream data, Collection<String> algos ) throws IOException { Map<String, Object> results = new LinkedHashMap<String, Object>(); @@ -135,10 +133,8 @@ public final class ChecksumUtils } } - InputStream in = null; - try + try ( InputStream in = data ) { - in = new FileInputStream( dataFile ); for ( byte[] buffer = new byte[ 32 * 1024 ];; ) { int read = in.read( buffer ); @@ -151,22 +147,6 @@ public final class ChecksumUtils digest.update( buffer, 0, read ); } } - in.close(); - in = null; - } - finally - { - try - { - if ( in != null ) - { - in.close(); - } - } - catch ( IOException e ) - { - // Suppressed due to an exception already thrown in the try block. - } } for ( Map.Entry<String, MessageDigest> entry : digests.entrySet() ) @@ -178,6 +158,7 @@ public final class ChecksumUtils return results; } + /** * Creates a hexadecimal representation of the specified bytes. Each byte is converted into a two-digit hex number diff --git a/maven-resolver-util/src/test/java/org/eclipse/aether/util/ChecksumUtilTest.java b/maven-resolver-util/src/test/java/org/eclipse/aether/util/ChecksumUtilTest.java index b249e82..08d1c64 100644 --- a/maven-resolver-util/src/test/java/org/eclipse/aether/util/ChecksumUtilTest.java +++ b/maven-resolver-util/src/test/java/org/eclipse/aether/util/ChecksumUtilTest.java @@ -37,30 +37,32 @@ import org.junit.Test; public class ChecksumUtilTest { - private File emptyFile; + private static final String EMPTY = "EMPTY"; + private static final String PATTERN = "PATTERN"; + private static final String TEXT = "TEXT"; + + private Map<String, File> files = new HashMap<>(3); + + private Map<String, byte[]> bytes = new HashMap<>(3); + + private static Map<String, String> emptyChecksums = new HashMap<>(); - private File patternFile; + private static Map<String, String> patternChecksums = new HashMap<>(); - private File textFile; + private static Map<String, String> textChecksums = new HashMap<>(); - private static Map<String, String> emptyFileChecksums = new HashMap<String, String>(); - - private static Map<String, String> patternFileChecksums = new HashMap<String, String>(); - - private static Map<String, String> textFileChecksums = new HashMap<String, String>(); - - private Map<File, Map<String, String>> sums = new HashMap<File, Map<String, String>>(); + private Map<String, Map<String, String>> sums = new HashMap<>(); @BeforeClass public static void beforeClass() throws IOException { - emptyFileChecksums.put( "MD5", "d41d8cd98f00b204e9800998ecf8427e" ); - emptyFileChecksums.put( "SHA-1", "da39a3ee5e6b4b0d3255bfef95601890afd80709" ); - patternFileChecksums.put( "MD5", "14f01d6c7de7d4cf0a4887baa3528b5a" ); - patternFileChecksums.put( "SHA-1", "feeeda19f626f9b0ef6cbf5948c1ec9531694295" ); - textFileChecksums.put( "MD5", "12582d1a662cefe3385f2113998e43ed" ); - textFileChecksums.put( "SHA-1", "a8ae272db549850eef2ff54376f8cac2770745ee" ); + emptyChecksums.put( "MD5", "d41d8cd98f00b204e9800998ecf8427e" ); + emptyChecksums.put( "SHA-1", "da39a3ee5e6b4b0d3255bfef95601890afd80709" ); + patternChecksums.put( "MD5", "14f01d6c7de7d4cf0a4887baa3528b5a" ); + patternChecksums.put( "SHA-1", "feeeda19f626f9b0ef6cbf5948c1ec9531694295" ); + textChecksums.put( "MD5", "12582d1a662cefe3385f2113998e43ed" ); + textChecksums.put( "SHA-1", "a8ae272db549850eef2ff54376f8cac2770745ee" ); } @Before @@ -69,15 +71,20 @@ public class ChecksumUtilTest { sums.clear(); - emptyFile = createTempFile( new byte[] {}, 0 ); - sums.put( emptyFile, emptyFileChecksums ); + byte[] emptyBytes = new byte[0]; + bytes.put( EMPTY, emptyBytes ); + files.put( EMPTY, createTempFile( emptyBytes, 0 ) ); + sums.put( EMPTY, emptyChecksums ); - patternFile = - createTempFile( new byte[] { 0, 1, 2, 4, 8, 16, 32, 64, 127, -1, -2, -4, -8, -16, -32, -64, -127 }, 1000 ); - sums.put( patternFile, patternFileChecksums ); + byte[] patternBytes = writeBytes( new byte[] { 0, 1, 2, 4, 8, 16, 32, 64, 127, -1, -2, -4, -8, -16, -32, -64, -127 }, 1000 ); + bytes.put( PATTERN, patternBytes ); + files.put( PATTERN, createTempFile( patternBytes, 1 ) ); + sums.put( PATTERN, patternChecksums ); - textFile = createTempFile( "the quick brown fox jumps over the lazy dog\n".getBytes( StandardCharsets.UTF_8 ), 500 ); - sums.put( textFile, textFileChecksums ); + byte[] textBytes = writeBytes( "the quick brown fox jumps over the lazy dog\n".getBytes( StandardCharsets.UTF_8 ), 500 ); + bytes.put( TEXT, textBytes ); + files.put( TEXT, createTempFile( textBytes, 1 ) ); + sums.put( TEXT, textChecksums ); } @@ -87,10 +94,10 @@ public class ChecksumUtilTest { Map<String, Object> checksums = null; - for ( File file : new File[] { emptyFile, patternFile, textFile } ) + for ( Map.Entry<String,File> fileEntry : files.entrySet() ) { - checksums = ChecksumUtils.calc( file, Arrays.asList( "SHA-1", "MD5" ) ); + checksums = ChecksumUtils.calc( fileEntry.getValue(), Arrays.asList( "SHA-1", "MD5" ) ); for ( Entry<String, Object> entry : checksums.entrySet() ) { @@ -99,11 +106,11 @@ public class ChecksumUtilTest throw (Throwable) entry.getValue(); } String actual = entry.getValue().toString(); - String expected = sums.get( file ).get( entry.getKey() ); - assertEquals( String.format( "checksums do not match for '%s', algorithm '%s'", file.getName(), + String expected = sums.get( fileEntry.getKey() ).get( entry.getKey() ); + assertEquals( String.format( "checksums do not match for '%s', algorithm '%s'", fileEntry.getValue().getName(), entry.getKey() ), expected, actual ); } - assertTrue( "Could not delete file", file.delete() ); + assertTrue( "Could not delete file", fileEntry.getValue().delete() ); } } @@ -111,7 +118,7 @@ public class ChecksumUtilTest public void testFileHandleLeakage() throws IOException { - for ( File file : new File[] { emptyFile, patternFile, textFile } ) + for ( File file : files.values() ) { for ( int i = 0; i < 150; i++ ) { @@ -182,5 +189,38 @@ public class ChecksumUtilTest assertEquals( "ff", ChecksumUtils.toHexString( new byte[] { -1 } ) ); assertEquals( "00017f", ChecksumUtils.toHexString( new byte[] { 0, 1, 127 } ) ); } + + @Test + public void testCalcWithByteArray() throws Throwable + { + Map<String, Object> checksums = null; + + for ( Map.Entry<String, byte[]> bytesEntry : bytes.entrySet() ) + { + checksums = ChecksumUtils.calc( bytesEntry.getValue(), Arrays.asList( "SHA-1", "MD5" ) ); + + for ( Entry<String, Object> entry : checksums.entrySet() ) + { + if ( entry.getValue() instanceof Throwable ) + { + throw (Throwable) entry.getValue(); + } + String actual = entry.getValue().toString(); + String expected = sums.get( bytesEntry.getKey() ).get( entry.getKey() ); + assertEquals( String.format( "checksums do not match for '%s', algorithm '%s'", bytesEntry.getKey(), + entry.getKey() ), expected, actual ); + } + } + } + private byte[] writeBytes( byte[] pattern, int repeat ) + { + byte[] result = new byte[pattern.length * repeat]; + for ( int i = 0; i < repeat; i++ ) + { + System.arraycopy( pattern, 0, result, i * pattern.length, pattern.length ); + } + return result; + } } +