You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by cs...@apache.org on 2022/04/14 12:08:11 UTC
[maven-resolver] 01/01: [EXPERIMENT] Dynamic LRM
This is an automated email from the ASF dual-hosted git repository.
cstamas pushed a commit to branch dynamic-lrm
in repository https://gitbox.apache.org/repos/asf/maven-resolver.git
commit 322b98048aecd6565090b63d8fb673c46ee3cd32
Author: Tamas Cservenak <ta...@cservenak.net>
AuthorDate: Thu Apr 14 14:07:20 2022 +0200
[EXPERIMENT] Dynamic LRM
---
.../eclipse/aether/impl/DefaultServiceLocator.java | 3 +
.../eclipse/aether/impl/guice/AetherModule.java | 7 +-
.../impl/DefaultDynamicPrefixComposerFactory.java | 120 +++++++
.../impl/DynamicLocalRepositoryManager.java | 152 +++++++++
...a => DynamicLocalRepositoryManagerFactory.java} | 49 ++-
.../internal/impl/DynamicPrefixComposer.java | 78 +++++
.../impl/DynamicPrefixComposerFactory.java | 38 +++
.../impl/EnhancedLocalRepositoryManager.java | 70 +++-
.../impl/SimpleLocalRepositoryManager.java | 34 +-
.../impl/SimpleLocalRepositoryManagerFactory.java | 2 +-
.../impl/DynamicLocalRepositoryManagerTest.java | 355 +++++++++++++++++++++
11 files changed, 869 insertions(+), 39 deletions(-)
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/DefaultServiceLocator.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/DefaultServiceLocator.java
index e3bf55a2..e021760f 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/DefaultServiceLocator.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/DefaultServiceLocator.java
@@ -35,7 +35,9 @@ import org.eclipse.aether.internal.impl.ArtifactPathComposer;
import org.eclipse.aether.internal.impl.DefaultArtifactPathComposer;
import org.eclipse.aether.internal.impl.DefaultArtifactResolver;
import org.eclipse.aether.internal.impl.DefaultChecksumPolicyProvider;
+import org.eclipse.aether.internal.impl.DefaultDynamicPrefixComposerFactory;
import org.eclipse.aether.internal.impl.DefaultTrackingFileManager;
+import org.eclipse.aether.internal.impl.DynamicPrefixComposerFactory;
import org.eclipse.aether.internal.impl.TrackingFileManager;
import org.eclipse.aether.internal.impl.checksum.DefaultChecksumAlgorithmFactorySelector;
import org.eclipse.aether.internal.impl.collect.DefaultDependencyCollector;
@@ -230,6 +232,7 @@ public final class DefaultServiceLocator
addService( NamedLockFactorySelector.class, SimpleNamedLockFactorySelector.class );
addService( ChecksumAlgorithmFactorySelector.class, DefaultChecksumAlgorithmFactorySelector.class );
addService( ArtifactPathComposer.class, DefaultArtifactPathComposer.class );
+ addService( DynamicPrefixComposerFactory.class, DefaultDynamicPrefixComposerFactory.class );
}
private <T> Entry<T> getEntry( Class<T> type, boolean create )
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/guice/AetherModule.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/guice/AetherModule.java
index 49e29adc..eeebaa29 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/guice/AetherModule.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/guice/AetherModule.java
@@ -42,7 +42,9 @@ import org.eclipse.aether.impl.RepositoryConnectorProvider;
import org.eclipse.aether.impl.RepositoryEventDispatcher;
import org.eclipse.aether.internal.impl.ArtifactPathComposer;
import org.eclipse.aether.internal.impl.DefaultArtifactPathComposer;
+import org.eclipse.aether.internal.impl.DefaultDynamicPrefixComposerFactory;
import org.eclipse.aether.internal.impl.DefaultTrackingFileManager;
+import org.eclipse.aether.internal.impl.DynamicPrefixComposerFactory;
import org.eclipse.aether.internal.impl.FileProvidedChecksumsSource;
import org.eclipse.aether.internal.impl.TrackingFileManager;
import org.eclipse.aether.internal.impl.checksum.Md5ChecksumAlgorithmFactory;
@@ -174,7 +176,10 @@ public class AetherModule
bind( OfflineController.class ) //
.to( DefaultOfflineController.class ).in( Singleton.class );
- bind( ArtifactPathComposer.class ).to( DefaultArtifactPathComposer.class ).in( Singleton.class );
+ bind( ArtifactPathComposer.class ) //
+ .to( DefaultArtifactPathComposer.class ).in( Singleton.class );
+ bind( DynamicPrefixComposerFactory.class ) //
+ .to( DefaultDynamicPrefixComposerFactory.class ).in( Singleton.class );
bind( LocalRepositoryProvider.class ) //
.to( DefaultLocalRepositoryProvider.class ).in( Singleton.class );
bind( LocalRepositoryManagerFactory.class ).annotatedWith( Names.named( "simple" ) ) //
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultDynamicPrefixComposerFactory.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultDynamicPrefixComposerFactory.java
new file mode 100644
index 00000000..e03a935c
--- /dev/null
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultDynamicPrefixComposerFactory.java
@@ -0,0 +1,120 @@
+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 javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.metadata.Metadata;
+import org.eclipse.aether.repository.RemoteRepository;
+
+/**
+ * Default implementation of {@link DynamicPrefixComposerFactory}.
+ *
+ * @since TBD
+ */
+@Singleton
+@Named
+public final class DefaultDynamicPrefixComposerFactory implements DynamicPrefixComposerFactory
+{
+ @Override
+ public DynamicPrefixComposer createComposer( RepositorySystemSession session )
+ {
+ return new Blow();
+ }
+
+ private static final class Blow implements DynamicPrefixComposer
+ {
+ @Override
+ public boolean isRemoteSplitByOrigin()
+ {
+ return true;
+ }
+
+ @Override
+ public String getPrefixForLocalArtifact( Artifact artifact )
+ {
+ return "local-" + ( artifact.isSnapshot() ? "snapshot" : "release" );
+ }
+
+ @Override
+ public String getPrefixForRemoteArtifact( Artifact artifact, RemoteRepository repository, String context )
+ {
+ return "remote-"
+ + ( artifact.isSnapshot() ? "snapshot" : "release" ) + '/'
+ + ( repository == null ? "norepository" : repository.getId() ) + '/'
+ + ( context == null || context.isEmpty() ? "noctx" : context );
+ }
+
+ @Override
+ public String getPrefixForLocalMetadata( Metadata metadata )
+ {
+ return "local-" + ( isSnapshot( metadata ) ? "snapshot" : "release" );
+ }
+
+ @Override
+ public String getPrefixForRemoteMetadata( Metadata metadata, RemoteRepository repository, String context )
+ {
+ return "remote-"
+ + ( isSnapshot( metadata ) ? "snapshot" : "release" ) + '/'
+ + ( repository == null ? "norepository" : repository.getId() ) + '/'
+ + ( context == null || context.isEmpty() ? "noctx" : context );
+ }
+
+ private boolean isSnapshot( Metadata metadata )
+ {
+ return metadata.getVersion() != null && metadata.getVersion().endsWith( "SNAPSHOT" );
+ }
+ }
+ private static final class NoopDynamicPrefixComposer implements DynamicPrefixComposer
+ {
+ @Override
+ public boolean isRemoteSplitByOrigin()
+ {
+ return false;
+ }
+
+ @Override
+ public String getPrefixForLocalArtifact( Artifact artifact )
+ {
+ return null;
+ }
+
+ @Override
+ public String getPrefixForRemoteArtifact( Artifact artifact, RemoteRepository repository, String context )
+ {
+ return null;
+ }
+
+ @Override
+ public String getPrefixForLocalMetadata( Metadata metadata )
+ {
+ return null;
+ }
+
+ @Override
+ public String getPrefixForRemoteMetadata( Metadata metadata, RemoteRepository repository, String context )
+ {
+ return null;
+ }
+ }
+}
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DynamicLocalRepositoryManager.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DynamicLocalRepositoryManager.java
new file mode 100644
index 00000000..b75b1d5e
--- /dev/null
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DynamicLocalRepositoryManager.java
@@ -0,0 +1,152 @@
+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.io.File;
+import java.util.Objects;
+
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.metadata.Metadata;
+import org.eclipse.aether.repository.LocalArtifactRegistration;
+import org.eclipse.aether.repository.LocalArtifactRequest;
+import org.eclipse.aether.repository.LocalArtifactResult;
+import org.eclipse.aether.repository.RemoteRepository;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Implementation of dynamic local repository manager, subject to change without prior notice.
+ *
+ * @see DynamicLocalRepositoryManagerFactory
+ * @since TBD
+ */
+class DynamicLocalRepositoryManager
+ extends EnhancedLocalRepositoryManager
+{
+ private final DynamicPrefixComposer dynamicPrefixComposer;
+
+ DynamicLocalRepositoryManager( File basedir,
+ ArtifactPathComposer artifactPathComposer,
+ RepositorySystemSession session,
+ TrackingFileManager trackingFileManager,
+ DynamicPrefixComposer dynamicPrefixComposer )
+ {
+ super( basedir, artifactPathComposer, session, trackingFileManager );
+ this.dynamicPrefixComposer = requireNonNull( dynamicPrefixComposer );
+ }
+
+ private String concatPaths( String prefix, String artifactPath )
+ {
+ if ( prefix == null || prefix.isEmpty() )
+ {
+ return artifactPath;
+ }
+ return prefix + '/' + artifactPath;
+ }
+
+ @Override
+ public String getPathForLocalArtifact( Artifact artifact )
+ {
+ return concatPaths(
+ dynamicPrefixComposer.getPrefixForLocalArtifact( artifact ),
+ super.getPathForLocalArtifact( artifact )
+ );
+ }
+
+ @Override
+ public String getPathForRemoteArtifact( Artifact artifact, RemoteRepository repository, String context )
+ {
+ return concatPaths(
+ dynamicPrefixComposer.getPrefixForRemoteArtifact( artifact, repository, context ),
+ super.getPathForRemoteArtifact( artifact, repository, context )
+ );
+ }
+
+ @Override
+ public String getPathForLocalMetadata( Metadata metadata )
+ {
+ return concatPaths(
+ dynamicPrefixComposer.getPrefixForLocalMetadata( metadata ),
+ super.getPathForLocalMetadata( metadata )
+ );
+ }
+
+ @Override
+ public String getPathForRemoteMetadata( Metadata metadata, RemoteRepository repository, String context )
+ {
+ return concatPaths(
+ dynamicPrefixComposer.getPrefixForRemoteMetadata( metadata, repository, context ),
+ super.getPathForRemoteMetadata( metadata, repository, context )
+ );
+ }
+
+ @Override
+ public LocalArtifactResult find( RepositorySystemSession session, LocalArtifactRequest request )
+ {
+ if ( !dynamicPrefixComposer.isRemoteSplitByOrigin() )
+ {
+ return super.find( session, request );
+ }
+ else
+ {
+ // we have split storage, so not "tracking" needed (to emulate split)
+ // try local first, and then try remotes as in request
+ Artifact artifact = request.getArtifact();
+ LocalArtifactResult result = new LocalArtifactResult( request );
+ String path = getPathForLocalArtifact( artifact );
+ File file = new File( getRepository().getBasedir(), path );
+
+ if ( Objects.equals( artifact.getVersion(), artifact.getBaseVersion() ) && file.isFile() )
+ {
+ result.setFile( file );
+ result.setAvailable( true );
+ }
+
+ if ( !result.isAvailable() )
+ {
+ for ( RemoteRepository repository : request.getRepositories() )
+ {
+ path = getPathForRemoteArtifact( artifact, repository, request.getContext() );
+ file = new File( getRepository().getBasedir(), path );
+ if ( file.isFile() )
+ {
+ result.setFile( file );
+ result.setAvailable( true );
+ result.setRepository( repository );
+ break;
+ }
+ }
+ }
+ return result;
+ }
+ }
+
+ @Override
+ public void add( RepositorySystemSession session, LocalArtifactRegistration request )
+ {
+ requireNonNull( session, "session cannot be null" );
+ requireNonNull( request, "request cannot be null" );
+ if ( !dynamicPrefixComposer.isRemoteSplitByOrigin() )
+ {
+ super.add( session, request );
+ }
+ }
+}
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerFactory.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DynamicLocalRepositoryManagerFactory.java
similarity index 50%
copy from maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerFactory.java
copy to maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DynamicLocalRepositoryManagerFactory.java
index d62301f5..46e72999 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerFactory.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DynamicLocalRepositoryManagerFactory.java
@@ -23,8 +23,6 @@ import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
-import java.util.Objects;
-
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.LocalRepositoryManager;
@@ -36,32 +34,51 @@ import org.eclipse.aether.spi.locator.ServiceLocator;
import static java.util.Objects.requireNonNull;
/**
- * Creates local repository managers for repository type {@code "simple"}.
+ * Creates dynamic local repository managers for repository types {@code "default"} or {@code "" (automatic)}. This is
+ * a completely new local repository implementation with capabilities to split "local" (locally installed) and "remote"
+ * (cached from remote) artifacts, and also split release and snapshot artifacts. It is able to split even
+ * by origin repositories as well. Resolution of locally cached artifacts will be rejected in case the current
+ * resolution request does not match the known source repositories of an artifact. If cache split by origin enabled,
+ * it physically separates artifact caches per remote repository, while if split not enabled, similar technique is
+ * used as in case of enhanced local repository, will emulate physically separated artifact caches per remote
+ * repository.
+ *
+ * @since TBD
*/
@Singleton
-@Named( "simple" )
-public class SimpleLocalRepositoryManagerFactory
+@Named( "dynamic" )
+public class DynamicLocalRepositoryManagerFactory
implements LocalRepositoryManagerFactory, Service
{
- private float priority;
+ private float priority = 11.0f;
private ArtifactPathComposer artifactPathComposer;
- public SimpleLocalRepositoryManagerFactory()
+ private DynamicPrefixComposerFactory dynamicPrefixComposerFactory;
+
+ private TrackingFileManager trackingFileManager;
+
+ public DynamicLocalRepositoryManagerFactory()
{
- // enable no-arg constructor
+ // no arg ctor for ServiceLocator
}
@Inject
- public SimpleLocalRepositoryManagerFactory( final ArtifactPathComposer artifactPathComposer )
+ public DynamicLocalRepositoryManagerFactory( final ArtifactPathComposer artifactPathComposer,
+ final TrackingFileManager trackingFileManager,
+ final DynamicPrefixComposerFactory dynamicPrefixComposerFactory )
{
this.artifactPathComposer = requireNonNull( artifactPathComposer );
+ this.trackingFileManager = requireNonNull( trackingFileManager );
+ this.dynamicPrefixComposerFactory = requireNonNull( dynamicPrefixComposerFactory );
}
@Override
public void initService( final ServiceLocator locator )
{
- this.artifactPathComposer = Objects.requireNonNull( locator.getService( ArtifactPathComposer.class ) );
+ this.artifactPathComposer = requireNonNull( locator.getService( ArtifactPathComposer.class ) );
+ this.trackingFileManager = requireNonNull( locator.getService( TrackingFileManager.class ) );
+ this.dynamicPrefixComposerFactory = requireNonNull( locator.getService( DynamicPrefixComposerFactory.class ) );
}
@Override
@@ -71,9 +88,15 @@ public class SimpleLocalRepositoryManagerFactory
requireNonNull( session, "session cannot be null" );
requireNonNull( repository, "repository cannot be null" );
- if ( "".equals( repository.getContentType() ) || "simple".equals( repository.getContentType() ) )
+ if ( "".equals( repository.getContentType() ) || "default".equals( repository.getContentType() ) )
{
- return new SimpleLocalRepositoryManager( repository.getBasedir(), "simple", artifactPathComposer );
+ return new DynamicLocalRepositoryManager(
+ repository.getBasedir(),
+ artifactPathComposer,
+ session,
+ trackingFileManager,
+ dynamicPrefixComposerFactory.createComposer( session )
+ );
}
else
{
@@ -93,7 +116,7 @@ public class SimpleLocalRepositoryManagerFactory
* @param priority The priority.
* @return This component for chaining, never {@code null}.
*/
- public SimpleLocalRepositoryManagerFactory setPriority( float priority )
+ public DynamicLocalRepositoryManagerFactory setPriority( float priority )
{
this.priority = priority;
return this;
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DynamicPrefixComposer.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DynamicPrefixComposer.java
new file mode 100644
index 00000000..d923e231
--- /dev/null
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DynamicPrefixComposer.java
@@ -0,0 +1,78 @@
+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 org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.metadata.Metadata;
+import org.eclipse.aether.repository.RemoteRepository;
+
+/**
+ * Composes prefixes for {@link DynamicLocalRepositoryManager}.
+ *
+ * @since TBD
+ */
+public interface DynamicPrefixComposer
+{
+ /**
+ * Returns {@code true} if methods
+ * {@link #getPrefixForRemoteArtifact(Artifact, RemoteRepository, String)} and
+ * {@link #getPrefixForRemoteMetadata(Metadata, RemoteRepository, String)} factor in into
+ * returned prefix the origin, thus physically separating the cache storage for origin.
+ *
+ * @return {@code true} if remote caches are physically separated by origin.
+ */
+ boolean isRemoteSplitByOrigin();
+
+ /**
+ * Gets the path prefix for a locally installed artifact.
+ *
+ * @param artifact The artifact for which to determine the prefix, must not be {@code null}.
+ * @return The prefix, may be {@code null} (note: {@code null}s and empty strings are treated equally).
+ */
+ String getPrefixForLocalArtifact( Artifact artifact );
+
+ /**
+ * Gets the path prefix for an artifact cached from a remote repository.
+ *
+ * @param artifact The artifact for which to determine the prefix, must not be {@code null}.
+ * @param repository The source repository of the artifact, must not be {@code null}.
+ * @param context The resolution context in which the artifact is being requested, may be {@code null}.
+ * @return The prefix, may be {@code null} (note: {@code null}s and empty strings are treated equally).
+ */
+ String getPrefixForRemoteArtifact( Artifact artifact, RemoteRepository repository, String context );
+
+ /**
+ * Gets the path prefix for locally installed metadata.
+ *
+ * @param metadata The metadata for which to determine the prefix, must not be {@code null}.
+ * @return The prefix, may be {@code null} (note: {@code null}s and empty strings are treated equally).
+ */
+ String getPrefixForLocalMetadata( Metadata metadata );
+
+ /**
+ * Gets the path prefix for metadata cached from a remote repository.
+ *
+ * @param metadata The metadata for which to determine the prefix, must not be {@code null}.
+ * @param repository The source repository of the metadata, must not be {@code null}.
+ * @param context The resolution context in which the metadata is being requested, may be {@code null}.
+ * @return The prefix, may be {@code null} (note: {@code null}s and empty strings are treated equally).
+ */
+ String getPrefixForRemoteMetadata( Metadata metadata, RemoteRepository repository, String context );
+}
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DynamicPrefixComposerFactory.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DynamicPrefixComposerFactory.java
new file mode 100644
index 00000000..25373091
--- /dev/null
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DynamicPrefixComposerFactory.java
@@ -0,0 +1,38 @@
+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 org.eclipse.aether.RepositorySystemSession;
+
+/**
+ * Creates instances of {@link DynamicPrefixComposer}.
+ *
+ * @since TBD
+ */
+public interface DynamicPrefixComposerFactory
+{
+ /**
+ * Creates {@link DynamicPrefixComposer} instance out of whatever configuration it finds in passed in session.
+ *
+ * @param session The repository session.
+ * @return The created instance, never {@code null}.
+ */
+ DynamicPrefixComposer createComposer( RepositorySystemSession session );
+}
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManager.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManager.java
index 13110db6..e09804fa 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManager.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManager.java
@@ -8,9 +8,9 @@ package org.eclipse.aether.internal.impl;
* 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
@@ -25,6 +25,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
+
import static java.util.Objects.requireNonNull;
import java.util.Objects;
@@ -44,7 +45,7 @@ import org.eclipse.aether.util.ConfigUtils;
* <code>_remote.repositories</code>, with content key as filename>repo_id and value as empty string. If a file has
* been installed in the repository, but not downloaded from a remote repository, it is tracked as empty repository id
* and always resolved. For example:
- *
+ *
* <pre>
* artifact-1.0.pom>=
* artifact-1.0.jar>=
@@ -54,11 +55,11 @@ import org.eclipse.aether.util.ConfigUtils;
* artifact-1.0-classifier.zip>central=
* artifact-1.0.pom>my_repo_id=
* </pre>
- *
+ *
* @see EnhancedLocalRepositoryManagerFactory
*/
class EnhancedLocalRepositoryManager
- extends SimpleLocalRepositoryManager
+ extends SimpleLocalRepositoryManager
{
private static final String LOCAL_REPO_ID = "";
@@ -75,22 +76,50 @@ class EnhancedLocalRepositoryManager
super( basedir, "enhanced", artifactPathComposer );
String filename = ConfigUtils.getString( session, "", "aether.enhancedLocalRepository.trackingFilename" );
if ( filename.isEmpty() || filename.contains( "/" ) || filename.contains( "\\" )
- || filename.contains( ".." ) )
+ || filename.contains( ".." ) )
{
filename = "_remote.repositories";
}
this.trackingFilename = filename;
- this.trackingFileManager = Objects.requireNonNull( trackingFileManager );
+ this.trackingFileManager = requireNonNull( trackingFileManager );
}
@Override
public LocalArtifactResult find( RepositorySystemSession session, LocalArtifactRequest request )
{
- String path = getPathForArtifact( request.getArtifact(), false );
+ Artifact artifact = request.getArtifact();
+ String path = getPathForLocalArtifact( artifact );
File file = new File( getRepository().getBasedir(), path );
LocalArtifactResult result = new LocalArtifactResult( request );
+ // request may ask for specific timestamped snapshot, while getPathForLocalArtifact turns it into -SNAPSHOT
+ if ( Objects.equals( artifact.getVersion(), artifact.getBaseVersion() ) )
+ {
+ checkFind( file, result );
+ }
+
+ if ( !result.isAvailable() )
+ {
+ for ( RemoteRepository repository : request.getRepositories() )
+ {
+ path = getPathForRemoteArtifact( artifact, repository, request.getContext() );
+ file = new File( getRepository().getBasedir(), path );
+
+ checkFind( file, result );
+
+ if ( result.isAvailable() )
+ {
+ break;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private void checkFind( File file, LocalArtifactResult result )
+ {
if ( file.isFile() )
{
result.setFile( file );
@@ -104,8 +133,8 @@ class EnhancedLocalRepositoryManager
}
else
{
- String context = request.getContext();
- for ( RemoteRepository repository : request.getRepositories() )
+ String context = result.getRequest().getContext();
+ for ( RemoteRepository repository : result.getRequest().getRepositories() )
{
if ( props.get( getKey( file, getRepositoryKey( repository, context ) ) ) != null )
{
@@ -126,8 +155,6 @@ class EnhancedLocalRepositoryManager
}
}
}
-
- return result;
}
@Override
@@ -142,7 +169,17 @@ class EnhancedLocalRepositoryManager
{
repositories = getRepositoryKeys( request.getRepository(), request.getContexts() );
}
- addArtifact( request.getArtifact(), repositories, request.getRepository() == null );
+ if ( request.getRepository() == null )
+ {
+ addArtifact( request.getArtifact(), repositories, null, null );
+ }
+ else
+ {
+ for ( String context : request.getContexts() )
+ {
+ addArtifact( request.getArtifact(), repositories, request.getRepository(), context );
+ }
+ }
}
private Collection<String> getRepositoryKeys( RemoteRepository repository, Collection<String> contexts )
@@ -160,9 +197,12 @@ class EnhancedLocalRepositoryManager
return keys;
}
- private void addArtifact( Artifact artifact, Collection<String> repositories, boolean local )
+ private void addArtifact( Artifact artifact, Collection<String> repositories, RemoteRepository repository,
+ String context )
{
- String path = getPathForArtifact( requireNonNull( artifact, "artifact cannot be null" ), local );
+ requireNonNull( artifact, "artifact cannot be null" );
+ String path = repository == null ? getPathForLocalArtifact( artifact )
+ : getPathForRemoteArtifact( artifact, repository, context );
File file = new File( getRepository().getBasedir(), path );
addRepo( file, repositories );
}
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManager.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManager.java
index c208e53c..b0147574 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManager.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManager.java
@@ -21,6 +21,8 @@ package org.eclipse.aether.internal.impl;
import java.io.File;
import static java.util.Objects.requireNonNull;
+
+import java.util.Objects;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -61,16 +63,11 @@ class SimpleLocalRepositoryManager
return repository;
}
- protected String getPathForArtifact( Artifact artifact, boolean local )
- {
- return artifactPathComposer.getPathForArtifact( artifact, local );
- }
-
@Override
public String getPathForLocalArtifact( Artifact artifact )
{
requireNonNull( artifact, "artifact cannot be null" );
- return getPathForArtifact( artifact, true );
+ return artifactPathComposer.getPathForArtifact( artifact, true );
}
@Override
@@ -78,7 +75,7 @@ class SimpleLocalRepositoryManager
{
requireNonNull( artifact, "artifact cannot be null" );
requireNonNull( repository, "repository cannot be null" );
- return getPathForArtifact( artifact, false );
+ return artifactPathComposer.getPathForArtifact( artifact, false );
}
@Override
@@ -141,16 +138,35 @@ class SimpleLocalRepositoryManager
{
requireNonNull( session, "session cannot be null" );
requireNonNull( request, "request cannot be null" );
- String path = getPathForArtifact( request.getArtifact(), false );
+ Artifact artifact = request.getArtifact();
+ String path = getPathForLocalArtifact( artifact );
File file = new File( getRepository().getBasedir(), path );
LocalArtifactResult result = new LocalArtifactResult( request );
- if ( file.isFile() )
+
+ // request may ask for specific timestamped snapshot, while getPathForLocalArtifact turns it into -SNAPSHOT
+ if ( Objects.equals( artifact.getVersion(), artifact.getBaseVersion() ) && file.isFile() )
{
result.setFile( file );
result.setAvailable( true );
}
+ if ( !result.isAvailable() )
+ {
+ for ( RemoteRepository repository : request.getRepositories() )
+ {
+ path = getPathForRemoteArtifact( artifact, repository, request.getContext() );
+ file = new File( getRepository().getBasedir(), path );
+ if ( file.isFile() )
+ {
+ result.setFile( file );
+ result.setAvailable( true );
+ break;
+ }
+ }
+
+ }
+
return result;
}
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerFactory.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerFactory.java
index d62301f5..d19af00e 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerFactory.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerFactory.java
@@ -45,7 +45,7 @@ public class SimpleLocalRepositoryManagerFactory
{
private float priority;
- private ArtifactPathComposer artifactPathComposer;
+ private ArtifactPathComposer artifactPathComposer = new DefaultArtifactPathComposer(); // to enable Maven tests
public SimpleLocalRepositoryManagerFactory()
{
diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DynamicLocalRepositoryManagerTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DynamicLocalRepositoryManagerTest.java
new file mode 100644
index 00000000..be22e8d2
--- /dev/null
+++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DynamicLocalRepositoryManagerTest.java
@@ -0,0 +1,355 @@
+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.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.internal.test.util.TestFileUtils;
+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.repository.LocalArtifactRegistration;
+import org.eclipse.aether.repository.LocalArtifactRequest;
+import org.eclipse.aether.repository.LocalArtifactResult;
+import org.eclipse.aether.repository.LocalMetadataRequest;
+import org.eclipse.aether.repository.LocalMetadataResult;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+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;
+
+public class DynamicLocalRepositoryManagerTest
+{
+
+ private Artifact artifact;
+
+ private Artifact snapshot;
+
+ private File basedir;
+
+ private DynamicLocalRepositoryManager manager;
+
+ private File artifactFile;
+
+ private RemoteRepository repository;
+
+ private String testContext = "project/compile";
+
+ private TrackingFileManager trackingFileManager;
+
+ private RepositorySystemSession session;
+
+ private Metadata metadata;
+
+ private Metadata noVerMetadata;
+
+ @Before
+ public void setup()
+ throws Exception
+ {
+ String url = TestFileUtils.createTempDir( "enhanced-remote-repo" ).toURI().toURL().toString();
+ repository =
+ new RemoteRepository.Builder( "enhanced-remote-repo", "default", url ).setRepositoryManager( true ).build();
+
+ artifact =
+ new DefaultArtifact( "gid", "aid", "", "jar", "1-test", Collections.<String, String> emptyMap(),
+ TestFileUtils.createTempFile( "artifact" ) );
+
+ snapshot =
+ new DefaultArtifact( "gid", "aid", "", "jar", "1.0-20120710.231549-9",
+ Collections.<String, String> emptyMap(), TestFileUtils.createTempFile( "artifact" ) );
+
+ metadata =
+ new DefaultMetadata( "gid", "aid", "1-test", "maven-metadata.xml", Nature.RELEASE,
+ TestFileUtils.createTempFile( "metadata" ) );
+
+ noVerMetadata =
+ new DefaultMetadata( "gid", "aid", null, "maven-metadata.xml", Nature.RELEASE,
+ TestFileUtils.createTempFile( "metadata" ) );
+
+ basedir = TestFileUtils.createTempDir( "enhanced-repo" );
+ session = TestUtils.newSession();
+ trackingFileManager = new DefaultTrackingFileManager();
+ manager = new DynamicLocalRepositoryManager(
+ basedir,
+ new DefaultArtifactPathComposer(),
+ session,
+ trackingFileManager,
+ new DefaultDynamicPrefixComposerFactory().createComposer( session )
+ );
+
+ artifactFile = new File( basedir, manager.getPathForLocalArtifact( artifact ) );
+ }
+
+ @After
+ public void tearDown()
+ throws Exception
+ {
+ TestFileUtils.deleteFile( basedir );
+ TestFileUtils.deleteFile( new File( new URI( repository.getUrl() ) ) );
+
+ session = null;
+ manager = null;
+ repository = null;
+ artifact = null;
+ }
+
+ private long addLocalArtifact( Artifact artifact )
+ throws IOException
+ {
+ manager.add( session, new LocalArtifactRegistration( artifact ) );
+ String path = manager.getPathForLocalArtifact( artifact );
+
+ return copy( artifact, path );
+ }
+
+ private long addRemoteArtifact( Artifact artifact )
+ throws IOException
+ {
+ Collection<String> contexts = Arrays.asList( testContext );
+ manager.add( session, new LocalArtifactRegistration( artifact, repository, contexts ) );
+ String path = manager.getPathForRemoteArtifact( artifact, repository, testContext );
+ return copy( artifact, path );
+ }
+
+ private long copy( Metadata metadata, String path )
+ throws IOException
+ {
+ if ( metadata.getFile() == null )
+ {
+ return -1L;
+ }
+ return TestFileUtils.copyFile( metadata.getFile(), new File( basedir, path ) );
+ }
+
+ private long copy( Artifact artifact, String path )
+ throws IOException
+ {
+ if ( artifact.getFile() == null )
+ {
+ return -1L;
+ }
+ File artifactFile = new File( basedir, path );
+ return TestFileUtils.copyFile( artifact.getFile(), artifactFile );
+ }
+
+ @Test
+ public void testGetPathForLocalArtifact()
+ {
+ Artifact artifact = new DefaultArtifact( "g.i.d:a.i.d:1.0-SNAPSHOT" );
+ assertEquals( "1.0-SNAPSHOT", artifact.getBaseVersion() );
+ assertEquals( "g/i/d/a.i.d/1.0-SNAPSHOT/a.i.d-1.0-SNAPSHOT.jar", manager.getPathForLocalArtifact( artifact ) );
+
+ artifact = new DefaultArtifact( "g.i.d:a.i.d:1.0-20110329.221805-4" );
+ assertEquals( "1.0-SNAPSHOT", artifact.getBaseVersion() );
+ assertEquals( "g/i/d/a.i.d/1.0-SNAPSHOT/a.i.d-1.0-SNAPSHOT.jar", manager.getPathForLocalArtifact( artifact ) );
+ }
+
+ @Test
+ public void testGetPathForRemoteArtifact()
+ {
+ RemoteRepository remoteRepo = new RemoteRepository.Builder( "repo", "default", "ram:/void" ).build();
+
+ Artifact artifact = new DefaultArtifact( "g.i.d:a.i.d:1.0-SNAPSHOT" );
+ assertEquals( "1.0-SNAPSHOT", artifact.getBaseVersion() );
+ assertEquals( "g/i/d/a.i.d/1.0-SNAPSHOT/a.i.d-1.0-SNAPSHOT.jar",
+ manager.getPathForRemoteArtifact( artifact, remoteRepo, "" ) );
+
+ artifact = new DefaultArtifact( "g.i.d:a.i.d:1.0-20110329.221805-4" );
+ assertEquals( "1.0-SNAPSHOT", artifact.getBaseVersion() );
+ assertEquals( "g/i/d/a.i.d/1.0-SNAPSHOT/a.i.d-1.0-20110329.221805-4.jar",
+ manager.getPathForRemoteArtifact( artifact, remoteRepo, "" ) );
+ }
+
+ @Test
+ public void testFindLocalArtifact()
+ throws Exception
+ {
+ addLocalArtifact( artifact );
+
+ LocalArtifactRequest request = new LocalArtifactRequest( artifact, null, null );
+ LocalArtifactResult result = manager.find( session, request );
+ assertTrue( result.isAvailable() );
+ assertNull( result.getRepository() );
+
+ snapshot = snapshot.setVersion( snapshot.getBaseVersion() );
+ addLocalArtifact( snapshot );
+
+ request = new LocalArtifactRequest( snapshot, null, null );
+ result = manager.find( session, request );
+ assertTrue( result.isAvailable() );
+ assertNull( result.getRepository() );
+ }
+
+ @Test
+ public void testFindRemoteArtifact()
+ throws Exception
+ {
+ addRemoteArtifact( artifact );
+
+ LocalArtifactRequest request = new LocalArtifactRequest( artifact, Arrays.asList( repository ), testContext );
+ LocalArtifactResult result = manager.find( session, request );
+ assertTrue( result.isAvailable() );
+ assertEquals( repository, result.getRepository() );
+
+ addRemoteArtifact( snapshot );
+
+ request = new LocalArtifactRequest( snapshot, Arrays.asList( repository ), testContext );
+ result = manager.find( session, request );
+ assertTrue( result.isAvailable() );
+ assertEquals( repository, result.getRepository() );
+ }
+
+ @Test
+ public void testDoNotFindDifferentContext()
+ throws Exception
+ {
+ addRemoteArtifact( artifact );
+
+ LocalArtifactRequest request = new LocalArtifactRequest( artifact, Arrays.asList( repository ), "different" );
+ LocalArtifactResult result = manager.find( session, request );
+ assertFalse( result.isAvailable() );
+ }
+
+ @Test
+ public void testDoNotFindNullFile()
+ throws Exception
+ {
+ artifact = artifact.setFile( null );
+ addLocalArtifact( artifact );
+
+ LocalArtifactRequest request = new LocalArtifactRequest( artifact, Arrays.asList( repository ), testContext );
+ LocalArtifactResult result = manager.find( session, request );
+ assertFalse( result.isAvailable() );
+ }
+
+ @Test
+ public void testDoNotFindDeletedFile()
+ throws Exception
+ {
+ addLocalArtifact( artifact );
+ assertTrue( "could not delete artifact file", artifactFile.delete() );
+
+ LocalArtifactRequest request = new LocalArtifactRequest( artifact, Arrays.asList( repository ), testContext );
+ LocalArtifactResult result = manager.find( session, request );
+ assertFalse( result.isAvailable() );
+ }
+
+ @Test
+ public void testFindUntrackedFile()
+ throws Exception
+ {
+ copy( artifact, manager.getPathForLocalArtifact( artifact ) );
+
+ LocalArtifactRequest request = new LocalArtifactRequest( artifact, Arrays.asList( repository ), testContext );
+ LocalArtifactResult result = manager.find( session, request );
+ assertTrue( result.isAvailable() );
+ }
+
+ private long addMetadata( Metadata metadata, RemoteRepository repo )
+ throws IOException
+ {
+ String path;
+ if ( repo == null )
+ {
+ path = manager.getPathForLocalMetadata( metadata );
+ }
+ else
+ {
+ path = manager.getPathForRemoteMetadata( metadata, repo, testContext );
+ }
+ System.err.println( path );
+
+ return copy( metadata, path );
+ }
+
+ @Test
+ public void testFindLocalMetadata()
+ throws Exception
+ {
+ addMetadata( metadata, null );
+
+ LocalMetadataRequest request = new LocalMetadataRequest( metadata, null, testContext );
+ LocalMetadataResult result = manager.find( session, request );
+
+ assertNotNull( result.getFile() );
+ }
+
+ @Test
+ public void testFindLocalMetadataNoVersion()
+ throws Exception
+ {
+ addMetadata( noVerMetadata, null );
+
+ LocalMetadataRequest request = new LocalMetadataRequest( noVerMetadata, null, testContext );
+ LocalMetadataResult result = manager.find( session, request );
+
+ assertNotNull( result.getFile() );
+ }
+
+ @Test
+ public void testDoNotFindRemoteMetadataDifferentContext()
+ throws Exception
+ {
+ addMetadata( noVerMetadata, repository );
+ addMetadata( metadata, repository );
+
+ LocalMetadataRequest request = new LocalMetadataRequest( noVerMetadata, repository, "different" );
+ LocalMetadataResult result = manager.find( session, request );
+ assertNull( result.getFile() );
+
+ request = new LocalMetadataRequest( metadata, repository, "different" );
+ result = manager.find( session, request );
+ assertNull( result.getFile() );
+ }
+
+ @Test
+ public void testFindArtifactUsesTimestampedVersion()
+ throws Exception
+ {
+ Artifact artifact = new DefaultArtifact( "g.i.d:a.i.d:1.0-SNAPSHOT" );
+ File file = new File( basedir, manager.getPathForLocalArtifact( artifact ) );
+ TestFileUtils.writeString( file, "test" );
+ addLocalArtifact( artifact );
+
+ artifact = artifact.setVersion( "1.0-20110329.221805-4" );
+ LocalArtifactRequest request = new LocalArtifactRequest();
+ request.setArtifact( artifact );
+ LocalArtifactResult result = manager.find( session, request );
+ assertNull( result.toString(), result.getFile() );
+ assertFalse( result.toString(), result.isAvailable() );
+ }
+
+}