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 }