View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.internal.impl.collect;
20  
21  import java.util.Collection;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Objects;
25  import java.util.concurrent.ConcurrentHashMap;
26  
27  import org.eclipse.aether.Keys;
28  import org.eclipse.aether.RepositoryCache;
29  import org.eclipse.aether.RepositorySystemSession;
30  import org.eclipse.aether.artifact.Artifact;
31  import org.eclipse.aether.collection.DependencyManager;
32  import org.eclipse.aether.collection.DependencySelector;
33  import org.eclipse.aether.collection.DependencyTraverser;
34  import org.eclipse.aether.collection.VersionFilter;
35  import org.eclipse.aether.graph.Dependency;
36  import org.eclipse.aether.graph.DependencyNode;
37  import org.eclipse.aether.repository.ArtifactRepository;
38  import org.eclipse.aether.repository.RemoteRepository;
39  import org.eclipse.aether.resolution.ArtifactDescriptorException;
40  import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
41  import org.eclipse.aether.resolution.ArtifactDescriptorResult;
42  import org.eclipse.aether.resolution.VersionRangeRequest;
43  import org.eclipse.aether.resolution.VersionRangeResult;
44  import org.eclipse.aether.util.ConfigUtils;
45  import org.eclipse.aether.util.concurrency.ConcurrentWeakCache;
46  import org.eclipse.aether.version.Version;
47  import org.eclipse.aether.version.VersionConstraint;
48  
49  /**
50   * Internal helper class for collector implementations.
51   */
52  public final class DataPool {
53      public static final String CONFIG_PROPS_PREFIX = DefaultDependencyCollector.CONFIG_PROPS_PREFIX + "pool.";
54  
55      /**
56       * Flag controlling interning data pool type used by dependency collector for Artifact instances, matters for
57       * heap consumption. By default, uses “weak” references (consume less heap). Using “hard” will make it much
58       * more memory aggressive and possibly faster (system and Java dependent). Supported values: "hard", "weak".
59       *
60       * @since 1.9.5
61       * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
62       * @configurationType {@link java.lang.String}
63       * @configurationDefaultValue {@link #WEAK}
64       */
65      public static final String CONFIG_PROP_COLLECTOR_POOL_ARTIFACT = CONFIG_PROPS_PREFIX + "artifact";
66  
67      /**
68       * Flag controlling interning data pool type used by dependency collector for Dependency instances, matters for
69       * heap consumption. By default, uses “weak” references (consume less heap). Using “hard” will make it much
70       * more memory aggressive and possibly faster (system and Java dependent). Supported values: "hard", "weak".
71       *
72       * @since 1.9.5
73       * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
74       * @configurationType {@link java.lang.String}
75       * @configurationDefaultValue {@link #WEAK}
76       */
77      public static final String CONFIG_PROP_COLLECTOR_POOL_DEPENDENCY = CONFIG_PROPS_PREFIX + "dependency";
78  
79      /**
80       * Flag controlling interning data pool type used by dependency collector for ArtifactDescriptor (POM) instances,
81       * matters for heap consumption. By default, uses “weak” references (consume less heap). Using “hard” will make it
82       * much more memory aggressive and possibly faster (system and Java dependent). Supported values: "hard", "weak".
83       *
84       * @since 1.9.5
85       * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
86       * @configurationType {@link java.lang.String}
87       * @configurationDefaultValue {@link #HARD}
88       */
89      public static final String CONFIG_PROP_COLLECTOR_POOL_DESCRIPTOR = CONFIG_PROPS_PREFIX + "descriptor";
90  
91      /**
92       * Flag controlling interning data pool type used by dependency lists collector for ArtifactDescriptor (POM) instances,
93       * matters for heap consumption. By default, uses “weak” references (consume less heap). Using “hard” will make it
94       * much more memory aggressive and possibly faster (system and Java dependent). Supported values: "hard", "weak".
95       *
96       * @since 1.9.22
97       * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
98       * @configurationType {@link java.lang.String}
99       * @configurationDefaultValue {@link #HARD}
100      */
101     public static final String CONFIG_PROP_COLLECTOR_POOL_DEPENDENCY_LISTS =
102             "aether.dependencyCollector.pool.dependencyLists";
103 
104     /**
105      * Flag controlling interning artifact descriptor dependencies.
106      *
107      * @since 1.9.22
108      * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
109      * @configurationType {@link java.lang.Boolean}
110      * @configurationDefaultValue false
111      */
112     public static final String CONFIG_PROP_COLLECTOR_POOL_INTERN_ARTIFACT_DESCRIPTOR_DEPENDENCIES =
113             "aether.dependencyCollector.pool.internArtifactDescriptorDependencies";
114 
115     /**
116      * Flag controlling interning artifact descriptor managed dependencies.
117      *
118      * @since 1.9.22
119      * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
120      * @configurationType {@link java.lang.Boolean}
121      * @configurationDefaultValue true
122      */
123     public static final String CONFIG_PROP_COLLECTOR_POOL_INTERN_ARTIFACT_DESCRIPTOR_MANAGED_DEPENDENCIES =
124             "aether.dependencyCollector.pool.internArtifactDescriptorManagedDependencies";
125 
126     private static final Object ARTIFACT_POOL = Keys.of(DataPool.class, "artifact");
127 
128     private static final Object DEPENDENCY_POOL = Keys.of(DataPool.class, "dependency");
129 
130     private static final Object DESCRIPTORS = Keys.of(DataPool.class, "descriptors");
131 
132     private static final Object DEPENDENCY_LISTS_POOL = Keys.of(DataPool.class, "dependencyLists");
133 
134     public static final ArtifactDescriptorResult NO_DESCRIPTOR =
135             new ArtifactDescriptorResult(new ArtifactDescriptorRequest());
136 
137     /**
138      * Artifact interning pool, lives across session (if session carries non-null {@link RepositoryCache}).
139      */
140     private final InternPool<Artifact, Artifact> artifacts;
141 
142     /**
143      * Dependency interning pool, lives across session (if session carries non-null {@link RepositoryCache}).
144      */
145     private final InternPool<Dependency, Dependency> dependencies;
146 
147     /**
148      * Descriptor interning pool, lives across session (if session carries non-null {@link RepositoryCache}).
149      */
150     private final InternPool<DescriptorKey, Descriptor> descriptors;
151 
152     /**
153      * {@link Dependency} list interning pool, lives across session (if session carries non-null {@link RepositoryCache}).
154      */
155     private final InternPool<List<Dependency>, List<Dependency>> dependencyLists;
156 
157     /**
158      * Constraint cache, lives during single collection invocation (same as this DataPool instance).
159      */
160     private final ConcurrentHashMap<Object, Constraint> constraints;
161 
162     /**
163      * DependencyNode cache, lives during single collection invocation (same as this DataPool instance).
164      */
165     private final ConcurrentHashMap<Object, List<DependencyNode>> nodes;
166 
167     private final boolean internArtifactDescriptorDependencies;
168 
169     private final boolean internArtifactDescriptorManagedDependencies;
170 
171     @SuppressWarnings("unchecked")
172     public DataPool(RepositorySystemSession session) {
173         final RepositoryCache cache = session.getCache();
174 
175         internArtifactDescriptorDependencies = ConfigUtils.getBoolean(
176                 session, false, CONFIG_PROP_COLLECTOR_POOL_INTERN_ARTIFACT_DESCRIPTOR_DEPENDENCIES);
177         internArtifactDescriptorManagedDependencies = ConfigUtils.getBoolean(
178                 session, true, CONFIG_PROP_COLLECTOR_POOL_INTERN_ARTIFACT_DESCRIPTOR_MANAGED_DEPENDENCIES);
179 
180         InternPool<Artifact, Artifact> artifactsPool;
181         InternPool<Dependency, Dependency> dependenciesPool;
182         InternPool<DescriptorKey, Descriptor> descriptorsPool;
183         InternPool<List<Dependency>, List<Dependency>> dependencyListsPool;
184         if (cache != null) {
185             artifactsPool = (InternPool<Artifact, Artifact>) cache.computeIfAbsent(
186                     session,
187                     ARTIFACT_POOL,
188                     () -> createPool(ConfigUtils.getString(session, WEAK, CONFIG_PROP_COLLECTOR_POOL_ARTIFACT)));
189             dependenciesPool = (InternPool<Dependency, Dependency>) cache.computeIfAbsent(
190                     session,
191                     DEPENDENCY_POOL,
192                     () -> createPool(ConfigUtils.getString(session, WEAK, CONFIG_PROP_COLLECTOR_POOL_DEPENDENCY)));
193             descriptorsPool = (InternPool<DescriptorKey, Descriptor>) cache.computeIfAbsent(
194                     session,
195                     DESCRIPTORS,
196                     () -> createPool(ConfigUtils.getString(session, HARD, CONFIG_PROP_COLLECTOR_POOL_DESCRIPTOR)));
197             dependencyListsPool = (InternPool<List<Dependency>, List<Dependency>>) cache.computeIfAbsent(
198                     session,
199                     DEPENDENCY_LISTS_POOL,
200                     () -> createPool(
201                             ConfigUtils.getString(session, HARD, CONFIG_PROP_COLLECTOR_POOL_DEPENDENCY_LISTS)));
202         } else {
203             artifactsPool = createPool(ConfigUtils.getString(session, WEAK, CONFIG_PROP_COLLECTOR_POOL_ARTIFACT));
204             dependenciesPool = createPool(ConfigUtils.getString(session, WEAK, CONFIG_PROP_COLLECTOR_POOL_DEPENDENCY));
205             descriptorsPool = createPool(ConfigUtils.getString(session, HARD, CONFIG_PROP_COLLECTOR_POOL_DESCRIPTOR));
206             dependencyListsPool =
207                     createPool(ConfigUtils.getString(session, HARD, CONFIG_PROP_COLLECTOR_POOL_DEPENDENCY_LISTS));
208         }
209 
210         this.artifacts = artifactsPool;
211         this.dependencies = dependenciesPool;
212         this.descriptors = descriptorsPool;
213         this.dependencyLists = dependencyListsPool;
214 
215         this.constraints = new ConcurrentHashMap<>(256);
216         this.nodes = new ConcurrentHashMap<>(256);
217     }
218 
219     public Artifact intern(Artifact artifact) {
220         return artifacts.intern(artifact, artifact);
221     }
222 
223     public Dependency intern(Dependency dependency) {
224         return dependencies.intern(dependency, dependency);
225     }
226 
227     public DescriptorKey toKey(ArtifactDescriptorRequest request) {
228         return new DescriptorKey(request.getArtifact(), request.getRepositories());
229     }
230 
231     public ArtifactDescriptorResult getDescriptor(DescriptorKey key, ArtifactDescriptorRequest request) {
232         Descriptor descriptor = descriptors.get(key);
233         if (descriptor != null) {
234             return descriptor.toResult(request);
235         }
236         return null;
237     }
238 
239     public void putDescriptor(DescriptorKey key, ArtifactDescriptorResult result) {
240         if (internArtifactDescriptorDependencies) {
241             result.setDependencies(intern(result.getDependencies()));
242         }
243         if (internArtifactDescriptorManagedDependencies) {
244             result.setManagedDependencies(intern(result.getManagedDependencies()));
245         }
246         descriptors.intern(key, new GoodDescriptor(result));
247     }
248 
249     public void putDescriptor(DescriptorKey key, ArtifactDescriptorException e) {
250         descriptors.intern(key, new BadDescriptor(e));
251     }
252 
253     /**
254      * Returns the failure reason of a previously cached bad descriptor, or {@code null} if the given key does not
255      * map to a cached failure. Only the exception message is retained (not the exception itself), to avoid pinning
256      * the originating request in the session-wide pool.
257      */
258     public String getDescriptorFailure(DescriptorKey key) {
259         Descriptor descriptor = descriptors.get(key);
260         if (descriptor instanceof BadDescriptor) {
261             return ((BadDescriptor) descriptor).reason;
262         }
263         return null;
264     }
265 
266     private List<Dependency> intern(List<Dependency> dependencies) {
267         return dependencyLists.intern(dependencies, dependencies);
268     }
269 
270     public Object toKey(VersionRangeRequest request) {
271         return new ConstraintKey(request);
272     }
273 
274     public VersionRangeResult getConstraint(Object key, VersionRangeRequest request) {
275         Constraint constraint = constraints.get(key);
276         if (constraint != null) {
277             return constraint.toResult(request);
278         }
279         return null;
280     }
281 
282     public void putConstraint(Object key, VersionRangeResult result) {
283         constraints.put(key, new Constraint(result));
284     }
285 
286     public Object toKey(
287             Artifact artifact,
288             List<RemoteRepository> repositories,
289             DependencySelector selector,
290             DependencyManager manager,
291             DependencyTraverser traverser,
292             VersionFilter filter) {
293         return new GraphKey(artifact, repositories, selector, manager, traverser, filter);
294     }
295 
296     public List<DependencyNode> getChildren(Object key) {
297         return nodes.get(key);
298     }
299 
300     public void putChildren(Object key, List<DependencyNode> children) {
301         nodes.put(key, children);
302     }
303 
304     public static final class DescriptorKey {
305         private final Artifact artifact;
306         private final List<RemoteRepository> repositories;
307         private final int hashCode;
308 
309         private DescriptorKey(Artifact artifact, List<RemoteRepository> repositories) {
310             this.artifact = artifact;
311             this.repositories = repositories;
312             this.hashCode = Objects.hashCode(artifact);
313         }
314 
315         @Override
316         public boolean equals(Object o) {
317             if (this == o) {
318                 return true;
319             }
320             if (o == null || getClass() != o.getClass()) {
321                 return false;
322             }
323             DescriptorKey that = (DescriptorKey) o;
324             return Objects.equals(artifact, that.artifact) && repositoriesEquals(repositories, that.repositories);
325         }
326 
327         @Override
328         public int hashCode() {
329             return hashCode;
330         }
331 
332         @Override
333         public String toString() {
334             return getClass().getSimpleName() + "{" + "artifact='" + artifact + '\'' + ", repositories='" + repositories
335                     + '\'' + '}';
336         }
337     }
338 
339     abstract static class Descriptor {
340         public abstract ArtifactDescriptorResult toResult(ArtifactDescriptorRequest request);
341     }
342 
343     static final class GoodDescriptor extends Descriptor {
344 
345         final Artifact artifact;
346 
347         final List<Artifact> relocations;
348 
349         final Collection<Artifact> aliases;
350 
351         final List<RemoteRepository> repositories;
352 
353         final List<Dependency> dependencies;
354 
355         final List<Dependency> managedDependencies;
356 
357         GoodDescriptor(ArtifactDescriptorResult result) {
358             artifact = result.getArtifact();
359             relocations = result.getRelocations();
360             aliases = result.getAliases();
361             dependencies = result.getDependencies();
362             managedDependencies = result.getManagedDependencies();
363             repositories = result.getRepositories();
364         }
365 
366         public ArtifactDescriptorResult toResult(ArtifactDescriptorRequest request) {
367             ArtifactDescriptorResult result = new ArtifactDescriptorResult(request);
368             result.setArtifact(artifact);
369             result.setRelocations(relocations);
370             result.setAliases(aliases);
371             result.setDependencies(dependencies);
372             result.setManagedDependencies(managedDependencies);
373             result.setRepositories(repositories);
374             return result;
375         }
376     }
377 
378     static final class BadDescriptor extends Descriptor {
379 
380         final String reason;
381 
382         BadDescriptor(ArtifactDescriptorException exception) {
383             this.reason = exception != null ? exception.getMessage() : null;
384         }
385 
386         public ArtifactDescriptorResult toResult(ArtifactDescriptorRequest request) {
387             return NO_DESCRIPTOR;
388         }
389     }
390 
391     private static final class Constraint {
392         final VersionRepo[] repositories;
393 
394         final VersionConstraint versionConstraint;
395 
396         Constraint(VersionRangeResult result) {
397             versionConstraint = result.getVersionConstraint();
398             List<Version> versions = result.getVersions();
399             repositories = new VersionRepo[versions.size()];
400             int i = 0;
401             for (Version version : versions) {
402                 repositories[i++] = new VersionRepo(version, result.getRepository(version));
403             }
404         }
405 
406         VersionRangeResult toResult(VersionRangeRequest request) {
407             VersionRangeResult result = new VersionRangeResult(request);
408             for (VersionRepo vr : repositories) {
409                 result.addVersion(vr.version);
410                 result.setRepository(vr.version, vr.repo);
411             }
412             result.setVersionConstraint(versionConstraint);
413             return result;
414         }
415 
416         static final class VersionRepo {
417             final Version version;
418 
419             final ArtifactRepository repo;
420 
421             VersionRepo(Version version, ArtifactRepository repo) {
422                 this.version = version;
423                 this.repo = repo;
424             }
425         }
426     }
427 
428     static final class ConstraintKey {
429         private final Artifact artifact;
430 
431         private final List<RemoteRepository> repositories;
432 
433         private final int hashCode;
434 
435         ConstraintKey(VersionRangeRequest request) {
436             artifact = request.getArtifact();
437             repositories = request.getRepositories();
438             hashCode = artifact.hashCode();
439         }
440 
441         @Override
442         public boolean equals(Object obj) {
443             if (obj == this) {
444                 return true;
445             } else if (!(obj instanceof ConstraintKey)) {
446                 return false;
447             }
448             ConstraintKey that = (ConstraintKey) obj;
449             return artifact.equals(that.artifact) && repositoriesEquals(repositories, that.repositories);
450         }
451 
452         @Override
453         public int hashCode() {
454             return hashCode;
455         }
456     }
457 
458     private static boolean repositoriesEquals(List<RemoteRepository> repos1, List<RemoteRepository> repos2) {
459         if (repos1.size() != repos2.size()) {
460             return false;
461         }
462         for (Iterator<RemoteRepository> it1 = repos1.iterator(), it2 = repos2.iterator();
463                 it1.hasNext() && it2.hasNext(); ) {
464             RemoteRepository repo1 = it1.next();
465             RemoteRepository repo2 = it2.next();
466             if (repo1.isRepositoryManager() != repo2.isRepositoryManager()) {
467                 return false;
468             }
469             if (repo1.isRepositoryManager()) {
470                 if (!repositoriesEquals(repo1.getMirroredRepositories(), repo2.getMirroredRepositories())) {
471                     return false;
472                 }
473             } else if (!repo1.getUrl().equals(repo2.getUrl())) {
474                 return false;
475             } else if (repo1.getPolicy(true).isEnabled()
476                     != repo2.getPolicy(true).isEnabled()) {
477                 return false;
478             } else if (repo1.getPolicy(false).isEnabled()
479                     != repo2.getPolicy(false).isEnabled()) {
480                 return false;
481             }
482         }
483         return true;
484     }
485 
486     static final class GraphKey {
487         private final Artifact artifact;
488 
489         private final List<RemoteRepository> repositories;
490 
491         private final DependencySelector selector;
492 
493         private final DependencyManager manager;
494 
495         private final DependencyTraverser traverser;
496 
497         private final VersionFilter filter;
498 
499         private final int hashCode;
500 
501         GraphKey(
502                 Artifact artifact,
503                 List<RemoteRepository> repositories,
504                 DependencySelector selector,
505                 DependencyManager manager,
506                 DependencyTraverser traverser,
507                 VersionFilter filter) {
508             this.artifact = artifact;
509             this.repositories = repositories;
510             this.selector = selector;
511             this.manager = manager;
512             this.traverser = traverser;
513             this.filter = filter;
514 
515             hashCode = Objects.hash(artifact, repositories, selector, manager, traverser, filter);
516         }
517 
518         @Override
519         public boolean equals(Object obj) {
520             if (obj == this) {
521                 return true;
522             } else if (!(obj instanceof GraphKey)) {
523                 return false;
524             }
525             GraphKey that = (GraphKey) obj;
526             return Objects.equals(artifact, that.artifact)
527                     && Objects.equals(repositories, that.repositories)
528                     && Objects.equals(selector, that.selector)
529                     && Objects.equals(manager, that.manager)
530                     && Objects.equals(traverser, that.traverser)
531                     && Objects.equals(filter, that.filter);
532         }
533 
534         @Override
535         public int hashCode() {
536             return hashCode;
537         }
538     }
539 
540     private static <K, V> InternPool<K, V> createPool(String type) {
541         if (HARD.equals(type)) {
542             return new HardInternPool<>();
543         } else if (WEAK.equals(type)) {
544             return new WeakInternPool<>();
545         } else {
546             throw new IllegalArgumentException("Unknown object pool type: '" + type + "'");
547         }
548     }
549 
550     public static final String HARD = "hard";
551 
552     public static final String WEAK = "weak";
553 
554     private interface InternPool<K, V> {
555         V get(K key);
556 
557         V intern(K key, V value);
558     }
559 
560     private static class HardInternPool<K, V> implements InternPool<K, V> {
561         private final ConcurrentHashMap<K, V> map = new ConcurrentHashMap<>(256);
562 
563         @Override
564         public V get(K key) {
565             return map.get(key);
566         }
567 
568         @Override
569         public V intern(K key, V value) {
570             return map.computeIfAbsent(key, k -> value);
571         }
572     }
573 
574     /**
575      * Intern pool backed by ConcurrentWeakCache with weak keys and weak values.
576      * Lock-free reads (ConcurrentHashMap.get is a volatile read, zero allocation via
577      * ThreadLocal lookup key), lock-striped writes, weak keys and values allow GC of
578      * interned objects when no longer strongly referenced.
579      * Uses putIfAbsent to guarantee concurrent callers for the same key get the same instance.
580      */
581     private static class WeakInternPool<K, V> implements InternPool<K, V> {
582         private final ConcurrentWeakCache<K, V> cache = new ConcurrentWeakCache<>(256);
583 
584         @Override
585         public V get(K key) {
586             return cache.get(key);
587         }
588 
589         @Override
590         public V intern(K key, V value) {
591             return cache.putIfAbsent(key, value);
592         }
593     }
594 }