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;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.io.IOException;
26  
27  import org.eclipse.aether.ConfigurationProperties;
28  import org.eclipse.aether.RepositorySystemSession;
29  import org.eclipse.aether.repository.LocalRepository;
30  import org.eclipse.aether.repository.LocalRepositoryManager;
31  import org.eclipse.aether.repository.NoLocalRepositoryManagerException;
32  import org.eclipse.aether.spi.localrepo.LocalRepositoryManagerFactory;
33  import org.eclipse.aether.spi.remoterepo.RepositoryKeyFunctionFactory;
34  import org.eclipse.aether.util.ConfigUtils;
35  
36  import static java.util.Objects.requireNonNull;
37  
38  /**
39   * Creates enhanced local repository managers for repository types {@code "default"} or {@code "" (automatic)}. Enhanced
40   * local repository manager is built upon the classical Maven 2.0 local repository structure but additionally keeps
41   * track of from what repositories a cached artifact was resolved. Resolution of locally cached artifacts will be
42   * rejected in case the current resolution request does not match the known source repositories of an artifact, thereby
43   * emulating physically separated artifact caches per remote repository.
44   */
45  @Singleton
46  @Named(EnhancedLocalRepositoryManagerFactory.NAME)
47  public class EnhancedLocalRepositoryManagerFactory implements LocalRepositoryManagerFactory {
48      public static final String NAME = "enhanced";
49  
50      static final String CONFIG_PROPS_PREFIX = ConfigurationProperties.PREFIX_LRM + NAME + ".";
51  
52      /**
53       * Filename of the file in which to track the remote repositories.
54       *
55       * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
56       * @configurationType {@link java.lang.String}
57       * @configurationDefaultValue {@link #DEFAULT_TRACKING_FILENAME}
58       */
59      public static final String CONFIG_PROP_TRACKING_FILENAME = CONFIG_PROPS_PREFIX + "trackingFilename";
60  
61      public static final String DEFAULT_TRACKING_FILENAME = "_remote.repositories";
62  
63      /**
64       * Whether to verify that the real (on-disk) path of a locally cached artifact matches the requested path
65       * spelling before the artifact is used. On case-insensitive or case/normalization-preserving filesystems (the
66       * macOS and Windows defaults) a file cached for one set of coordinates also answers lookups for coordinates
67       * that differ only in case or Unicode normalization, while the repository tracking data is compared exactly:
68       * such an aliased file is treated as present-but-untracked and accepted with no download and no checksum
69       * verification, letting case-colliding coordinates poison distinct GAVs. When enabled (the default), an
70       * artifact whose on-disk path spelling differs from the requested one is treated as not present, forcing a
71       * proper download. Disable only if the local repository intentionally contains symbolic links below its base
72       * directory (a symlinked base directory itself is supported either way).
73       *
74       * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
75       * @configurationType {@link java.lang.Boolean}
76       * @configurationDefaultValue {@link #DEFAULT_VERIFY_REAL_PATH}
77       * @since 2.0.23
78       */
79      public static final String CONFIG_PROP_VERIFY_REAL_PATH = CONFIG_PROPS_PREFIX + "verifyRealPath";
80  
81      public static final boolean DEFAULT_VERIFY_REAL_PATH = true;
82  
83      /**
84       * Marks whether the local repository is meant to be shared (or was shared) with legacy Maven 3.9 or older
85       * versions. Maven 3.9 and older versions suffer from "impostor" problem, where artifact and metadata origin was
86       * tracked only by the remote repository ID, where two remote repositories may share same ID but different URLs,
87       * in fact they may be completely unrelated to each other (ID clash by mistake), or, it may be due some sort of
88       * "impostor" attempt, where a malicious repository may pretend like some other repository.
89       * Right now, we intentionally default to {@code true} to ease users transitioning, and Resolver 2 will retain
90       * this "old" behavior (will observe legacy tracking entries and will store remote metadata as before). But,
91       * at some point in the future, the default value will be changed to {@code false} (and same change is warmly
92       * recommended for modern Maven users, who do not intend to share local repository with older Maven versions).
93       * When this configuration set to {@code false}, the "repository key" is not ID only anymore, but is changed
94       * to {@code $id-sha1($url)} form, and this key is used in "origin tracking" entries and in caching remote
95       * Maven Repository Metadata XML files as well, guaranteeing they are not mixed in case of same IDs.
96       *
97       * @see ConfigurationProperties#REPOSITORY_SYSTEM_REPOSITORY_KEY_FUNCTION
98       * @see ConfigurationProperties#REPOSITORY_TRACKING_REPOSITORY_KEY_FUNCTION
99       * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
100      * @configurationType {@link java.lang.Boolean}
101      * @configurationDefaultValue {@link #DEFAULT_LEGACY_LOCAL_REPOSITORY}
102      * @since 2.0.23
103      */
104     public static final String CONFIG_PROP_LEGACY_LOCAL_REPOSITORY = CONFIG_PROPS_PREFIX + "legacyLocalRepository";
105 
106     public static final boolean DEFAULT_LEGACY_LOCAL_REPOSITORY = true;
107 
108     private float priority = 10.0f;
109 
110     private final LocalPathComposer localPathComposer;
111 
112     private final TrackingFileManager trackingFileManager;
113 
114     private final LocalPathPrefixComposerFactory localPathPrefixComposerFactory;
115 
116     private final RepositoryKeyFunctionFactory repositoryKeyFunctionFactory;
117 
118     @Inject
119     public EnhancedLocalRepositoryManagerFactory(
120             final LocalPathComposer localPathComposer,
121             final TrackingFileManager trackingFileManager,
122             final LocalPathPrefixComposerFactory localPathPrefixComposerFactory,
123             final RepositoryKeyFunctionFactory repositoryKeyFunctionFactory) {
124         this.localPathComposer = requireNonNull(localPathComposer);
125         this.trackingFileManager = requireNonNull(trackingFileManager);
126         this.localPathPrefixComposerFactory = requireNonNull(localPathPrefixComposerFactory);
127         this.repositoryKeyFunctionFactory = requireNonNull(repositoryKeyFunctionFactory);
128     }
129 
130     @Override
131     public LocalRepositoryManager newInstance(RepositorySystemSession session, LocalRepository repository)
132             throws NoLocalRepositoryManagerException {
133         requireNonNull(session, "session cannot be null");
134         requireNonNull(repository, "repository cannot be null");
135 
136         String trackingFilename = ConfigUtils.getString(session, "", CONFIG_PROP_TRACKING_FILENAME);
137         if (trackingFilename.isEmpty()
138                 || trackingFilename.contains("/")
139                 || trackingFilename.contains("\\")
140                 || trackingFilename.contains("..")) {
141             trackingFilename = DEFAULT_TRACKING_FILENAME;
142         }
143         boolean legacyLocalRepository =
144                 ConfigUtils.getBoolean(session, DEFAULT_LEGACY_LOCAL_REPOSITORY, CONFIG_PROP_LEGACY_LOCAL_REPOSITORY);
145 
146         if ("".equals(repository.getContentType()) || "default".equals(repository.getContentType())) {
147             try {
148                 return new EnhancedLocalRepositoryManager(
149                         repository.getBasePath(),
150                         localPathComposer,
151                         repositoryKeyFunctionFactory.trackingRepositoryKeyFunction(session),
152                         trackingFilename,
153                         legacyLocalRepository,
154                         trackingFileManager,
155                         localPathPrefixComposerFactory.createComposer(session));
156             } catch (IOException e) {
157                 throw new NoLocalRepositoryManagerException(repository, e);
158             }
159         } else {
160             throw new NoLocalRepositoryManagerException(repository);
161         }
162     }
163 
164     @Override
165     public float getPriority() {
166         return priority;
167     }
168 
169     /**
170      * Sets the priority of this component.
171      *
172      * @param priority The priority.
173      * @return This component for chaining, never {@code null}.
174      */
175     public EnhancedLocalRepositoryManagerFactory setPriority(float priority) {
176         this.priority = priority;
177         return this;
178     }
179 }