diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultUpdateCheckManager.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultUpdateCheckManager.java index 367bc2b2c..fa7226dda 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultUpdateCheckManager.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultUpdateCheckManager.java @@ -23,12 +23,7 @@ import javax.inject.Singleton; import java.io.File; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.TreeSet; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.aether.RepositorySystemSession; @@ -87,6 +82,20 @@ public String toString() { private static final int STATE_DISABLED = 2; + /** + * Configuration property key for effective {@link UpdatePolicyScope}, defaults to {@link #DEFAULT_UPDATE_POLICY_SCOPE}. + * + * @since TBD + */ + static final String CONFIG_PROP_UPDATE_POLICY_SCOPE = "aether.updateCheckManager.updatePolicyScope"; + + /** + * The default value for {@link #CONFIG_PROP_UPDATE_POLICY_SCOPE}, {@link UpdatePolicyScope#METADATA}. + * + * @since TBD + */ + static final UpdatePolicyScope DEFAULT_UPDATE_POLICY_SCOPE = UpdatePolicyScope.METADATA; + /** * This "last modified" timestamp is used when no local file is present, signaling "first attempt" to cache a file, * but as it is not present, outcome is simply always "go get it". @@ -106,6 +115,33 @@ public String toString() { */ private static final long TS_UNKNOWN = 1L; + /** + * Update policy scope defines to what the policy is applied. If policy is not applied, the presence or absence + * matters only. + * + * @since TBD + */ + enum UpdatePolicyScope { + ALL(true, true), // Maven3.x behaviour: applies to metadata and artifacts + METADATA(false, true); // Applies ONLY to metadata (as artifacts are immutable) + + private final boolean applyToArtifact; + private final boolean applyToMetadata; + + UpdatePolicyScope(boolean applyToArtifact, boolean applyToMetadata) { + this.applyToArtifact = applyToArtifact; + this.applyToMetadata = applyToMetadata; + } + + public boolean isApplyToArtifact() { + return applyToArtifact; + } + + public boolean isApplyToMetadata() { + return applyToMetadata; + } + } + public DefaultUpdateCheckManager() { // default ctor for ServiceLocator } @@ -116,6 +152,7 @@ public DefaultUpdateCheckManager() { setUpdatePolicyAnalyzer(updatePolicyAnalyzer); } + @Override public void initService(ServiceLocator locator) { setTrackingFileManager(locator.getService(TrackingFileManager.class)); setUpdatePolicyAnalyzer(locator.getService(UpdatePolicyAnalyzer.class)); @@ -131,6 +168,7 @@ public DefaultUpdateCheckManager setUpdatePolicyAnalyzer(UpdatePolicyAnalyzer up return this; } + @Override public void checkArtifact(RepositorySystemSession session, UpdateCheck check) { requireNonNull(session, "session cannot be null"); requireNonNull(check, "check cannot be null"); @@ -176,6 +214,7 @@ public void checkArtifact(RepositorySystemSession session, UpdateCheck check) { requireNonNull(session, "session cannot be null"); requireNonNull(check, "check cannot be null"); @@ -281,6 +324,7 @@ public void checkMetadata(RepositorySystemSession session, UpdateCheck check) { requireNonNull(session, "session cannot be null"); requireNonNull(check, "check cannot be null"); @@ -487,6 +535,7 @@ private boolean hasErrors(Properties props) { return false; } + @Override public void touchMetadata(RepositorySystemSession session, UpdateCheck check) { requireNonNull(session, "session cannot be null"); requireNonNull(check, "check cannot be null"); @@ -526,4 +575,27 @@ private Properties write(File touchFile, String dataKey, String transferKey, Exc return trackingFileManager.update(touchFile, updates); } + + /** + * Returns the configured {@link UpdatePolicyScope} or default value {@link #DEFAULT_UPDATE_POLICY_SCOPE}. + */ + static UpdatePolicyScope getUpdatePolicyScope(RepositorySystemSession session, RemoteRepository repository) { + String scope = ConfigUtils.getString( + session, + "", + CONFIG_PROP_UPDATE_POLICY_SCOPE + "." + repository.getId(), + CONFIG_PROP_UPDATE_POLICY_SCOPE); + if (scope != null && !scope.isEmpty()) { + try { + return UpdatePolicyScope.valueOf(scope.toUpperCase(Locale.ENGLISH)); + } catch (IllegalArgumentException e) { + LOGGER.warn( + "Illegal value for '{}': '{}' (allowed case-insensitive values are {})", + CONFIG_PROP_UPDATE_POLICY_SCOPE, + scope, + UpdatePolicyScope.values()); + } + } + return DEFAULT_UPDATE_POLICY_SCOPE; + } } diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultUpdateCheckManagerTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultUpdateCheckManagerTest.java index ab61d5754..e06caba71 100644 --- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultUpdateCheckManagerTest.java +++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultUpdateCheckManagerTest.java @@ -20,9 +20,7 @@ import java.io.File; import java.net.URI; -import java.util.Calendar; -import java.util.Date; -import java.util.TimeZone; +import java.util.*; import org.eclipse.aether.DefaultRepositorySystemSession; import org.eclipse.aether.RepositorySystemSession; @@ -43,15 +41,25 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import static org.junit.Assert.*; +import static org.junit.Assume.assumeTrue; /** */ +@RunWith(Parameterized.class) public class DefaultUpdateCheckManagerTest { + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList(new Object[][] {{""}, {"all"}, {"metadata"}}); + } private static final long HOUR = 60L * 60L * 1000L; + private final String updatePolicyScopeString; + private DefaultUpdateCheckManager manager; private DefaultRepositorySystemSession session; @@ -62,6 +70,12 @@ public class DefaultUpdateCheckManagerTest { private Artifact artifact; + private DefaultUpdateCheckManager.UpdatePolicyScope updatePolicyScope; + + public DefaultUpdateCheckManagerTest(String updatePolicyScopeString) { + this.updatePolicyScopeString = updatePolicyScopeString; + } + @Before public void setup() throws Exception { File dir = TestFileUtils.createTempFile(""); @@ -73,6 +87,7 @@ public void setup() throws Exception { TestFileUtils.writeString(artifactFile, "artifact"); session = TestUtils.newSession(); + session.setConfigProperty(DefaultUpdateCheckManager.CONFIG_PROP_UPDATE_POLICY_SCOPE, updatePolicyScopeString); repository = new RemoteRepository.Builder( "id", "default", @@ -84,6 +99,7 @@ public void setup() throws Exception { metadata = new DefaultMetadata( "gid", "aid", "ver", "maven-metadata.xml", Metadata.Nature.RELEASE_OR_SNAPSHOT, metadataFile); artifact = new DefaultArtifact("gid", "aid", "", "ext", "ver").setFile(artifactFile); + updatePolicyScope = DefaultUpdateCheckManager.getUpdatePolicyScope(session, repository); } @After @@ -445,6 +461,7 @@ public void testCheckArtifactFailOnNoFile() { @Test public void testCheckArtifactUpdatePolicyRequired() { + assumeTrue(updatePolicyScope.isApplyToArtifact()); UpdateCheck check = newArtifactCheck(); check.setItem(artifact); check.setFile(artifact.getFile()); @@ -500,6 +517,7 @@ public void testCheckArtifactUpdatePolicyNotRequired() { @Test public void testCheckArtifact() { + assumeTrue(updatePolicyScope.isApplyToArtifact()); UpdateCheck check = newArtifactCheck(); long fifteenMinutes = new Date().getTime() - (15L * 60L * 1000L); check.getFile().setLastModified(fifteenMinutes); @@ -613,6 +631,7 @@ public void testCheckArtifactErrorFromRepoCachingDisabled() { @Test public void testCheckArtifactAtMostOnceDuringSessionEvenIfUpdatePolicyAlways() { + assumeTrue(updatePolicyScope.isApplyToArtifact()); UpdateCheck check = newArtifactCheck(); check.setPolicy(RepositoryPolicy.UPDATE_POLICY_ALWAYS); @@ -629,6 +648,7 @@ public void testCheckArtifactAtMostOnceDuringSessionEvenIfUpdatePolicyAlways() { @Test public void testCheckArtifactSessionStateModes() { + assumeTrue(updatePolicyScope.isApplyToArtifact()); UpdateCheck check = newArtifactCheck(); check.setPolicy(RepositoryPolicy.UPDATE_POLICY_ALWAYS); manager.touchArtifact(session, check); @@ -677,6 +697,7 @@ public void testCheckArtifactAtMostOnceDuringSessionEvenIfUpdatePolicyAlways_Inv @Test public void testCheckArtifactAtMostOnceDuringSessionEvenIfUpdatePolicyAlways_DifferentRepoIdSameUrl() { + assumeTrue(updatePolicyScope.isApplyToArtifact()); UpdateCheck check = newArtifactCheck(); check.setPolicy(RepositoryPolicy.UPDATE_POLICY_ALWAYS); diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md index 4e193dfb4..bc92c7d62 100644 --- a/src/site/markdown/configuration.md +++ b/src/site/markdown/configuration.md @@ -108,6 +108,7 @@ Option | Type | Description | Default Value | Supports Repo ID Suffix `aether.trustedChecksumsSource.summaryFile.basedir` | String | The basedir path for `summaryFile` trusted checksum source. If relative, resolved against local repository root, if absolute, used as is. | `".checksums"` | no `aether.trustedChecksumsSource.summaryFile.originAware` | boolean | Is trusted checksum source origin aware (factors in Repository ID into path) or not. | `true` | no `aether.updateCheckManager.sessionState` | String | Manages the session state, i.e. influences if the same download requests to artifacts/metadata will happen multiple times within the same RepositorySystemSession. If `"enabled"` will enable the session state. If `"bypass"` will enable bypassing (i.e. store all artifact ids/metadata ids which have been updates but not evaluating those). All other values lead to disabling the session state completely. | `"enabled"` | no +`aether.updateCheckManager.updatePolicyScope` | String | Defines the scope to what update policy should be applied to. Accepted values are "all" (Maven3 behaviour, artifacts and metadata both are checked for updates) and "metadata" (only metadata is checked for updates, assuming artifacts are immutable). | `"metadata"` | yes All properties which have `yes` in the column `Supports Repo ID Suffix` can be optionally configured specifically for a repository id. In that case the configuration property needs to be suffixed with a period followed by the repository id of the repository to configure, e.g. `aether.connector.http.headers.central` for repository with id `central`.