1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.aether.internal.impl.synccontext.named;
20
21 import java.util.ArrayDeque;
22 import java.util.Collection;
23 import java.util.Deque;
24 import java.util.concurrent.TimeUnit;
25 import java.util.stream.Collectors;
26
27 import org.eclipse.aether.ConfigurationProperties;
28 import org.eclipse.aether.RepositorySystemSession;
29 import org.eclipse.aether.SyncContext;
30 import org.eclipse.aether.artifact.Artifact;
31 import org.eclipse.aether.metadata.Metadata;
32 import org.eclipse.aether.named.NamedLock;
33 import org.eclipse.aether.named.NamedLockFactory;
34 import org.eclipse.aether.named.NamedLockKey;
35 import org.eclipse.aether.named.providers.FileLockNamedLockFactory;
36 import org.eclipse.aether.util.ConfigUtils;
37 import org.eclipse.aether.util.artifact.ArtifactIdUtils;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 import static java.util.Objects.requireNonNull;
42
43
44
45
46 public final class NamedLockFactoryAdapter {
47 public static final String CONFIG_PROPS_PREFIX = ConfigurationProperties.PREFIX_SYNC_CONTEXT + "named.";
48
49
50
51
52
53
54
55
56
57 public static final String CONFIG_PROP_TIME = CONFIG_PROPS_PREFIX + "time";
58
59 public static final long DEFAULT_TIME = 900L;
60
61
62
63
64
65
66
67
68
69 public static final String CONFIG_PROP_TIME_UNIT = CONFIG_PROPS_PREFIX + "time.unit";
70
71 public static final String DEFAULT_TIME_UNIT = "SECONDS";
72
73
74
75
76
77
78
79
80
81 public static final String CONFIG_PROP_RETRY = CONFIG_PROPS_PREFIX + "retry";
82
83 public static final int DEFAULT_RETRY = 1;
84
85
86
87
88
89
90
91
92
93 public static final String CONFIG_PROP_RETRY_WAIT = CONFIG_PROPS_PREFIX + "retry.wait";
94
95 public static final long DEFAULT_RETRY_WAIT = 200L;
96
97 private final NameMapper nameMapper;
98
99 private final NamedLockFactory namedLockFactory;
100
101 public NamedLockFactoryAdapter(final NameMapper nameMapper, final NamedLockFactory namedLockFactory) {
102 this.nameMapper = requireNonNull(nameMapper);
103 this.namedLockFactory = requireNonNull(namedLockFactory);
104
105 if (this.namedLockFactory instanceof FileLockNamedLockFactory && !this.nameMapper.isFileSystemFriendly()) {
106 throw new IllegalArgumentException(
107 "Misconfiguration: FileLockNamedLockFactory lock factory requires FS friendly NameMapper");
108 }
109 }
110
111 public SyncContext newInstance(final RepositorySystemSession session, final boolean shared) {
112 return new AdaptedLockSyncContext(session, shared, nameMapper, namedLockFactory);
113 }
114
115
116
117
118 public NameMapper getNameMapper() {
119 return nameMapper;
120 }
121
122
123
124
125 public NamedLockFactory getNamedLockFactory() {
126 return namedLockFactory;
127 }
128
129 public String toString() {
130 return getClass().getSimpleName()
131 + "(nameMapper=" + nameMapper
132 + ", namedLockFactory=" + namedLockFactory
133 + ")";
134 }
135
136 private static class AdaptedLockSyncContext implements SyncContext {
137 private static final Logger LOGGER = LoggerFactory.getLogger(AdaptedLockSyncContext.class);
138
139 private final RepositorySystemSession session;
140
141 private final boolean shared;
142
143 private final NameMapper lockNaming;
144
145 private final NamedLockFactory namedLockFactory;
146
147 private final long time;
148
149 private final TimeUnit timeUnit;
150
151 private final int retry;
152
153 private final long retryWait;
154
155 private final Deque<NamedLock> locks;
156
157 private AdaptedLockSyncContext(
158 final RepositorySystemSession session,
159 final boolean shared,
160 final NameMapper lockNaming,
161 final NamedLockFactory namedLockFactory) {
162 this.session = session;
163 this.shared = shared;
164 this.lockNaming = lockNaming;
165 this.namedLockFactory = namedLockFactory;
166 this.time = getTime(session, DEFAULT_TIME, CONFIG_PROP_TIME);
167 this.timeUnit = getTimeUnit(session);
168 this.retry = getRetry(session);
169 this.retryWait = getRetryWait(session);
170 this.locks = new ArrayDeque<>();
171
172 if (time < 0L) {
173 throw new IllegalArgumentException(CONFIG_PROP_TIME + " value cannot be negative");
174 }
175 if (retry < 0L) {
176 throw new IllegalArgumentException(CONFIG_PROP_RETRY + " value cannot be negative");
177 }
178 if (retryWait < 0L) {
179 throw new IllegalArgumentException(CONFIG_PROP_RETRY_WAIT + " value cannot be negative");
180 }
181 }
182
183 private long getTime(final RepositorySystemSession session, long defaultValue, String... keys) {
184 return ConfigUtils.getLong(session, defaultValue, keys);
185 }
186
187 private TimeUnit getTimeUnit(final RepositorySystemSession session) {
188 return TimeUnit.valueOf(ConfigUtils.getString(session, DEFAULT_TIME_UNIT, CONFIG_PROP_TIME_UNIT));
189 }
190
191 private int getRetry(final RepositorySystemSession session) {
192 return ConfigUtils.getInteger(session, DEFAULT_RETRY, CONFIG_PROP_RETRY);
193 }
194
195 private long getRetryWait(final RepositorySystemSession session) {
196 return ConfigUtils.getLong(session, DEFAULT_RETRY_WAIT, CONFIG_PROP_RETRY_WAIT);
197 }
198
199 @Override
200 public void acquire(Collection<? extends Artifact> artifacts, Collection<? extends Metadata> metadatas) {
201 Collection<NamedLockKey> keys = lockNaming.nameLocks(session, artifacts, metadatas);
202 if (keys.isEmpty()) {
203 return;
204 }
205
206 final String timeStr = time + " " + timeUnit;
207 final String lockKind = shared ? "shared" : "exclusive";
208 final NamedLock namedLock = namedLockFactory.getLock(keys);
209 if (LOGGER.isTraceEnabled()) {
210 LOGGER.trace(
211 "Need {} lock for {} from {}",
212 lockKind,
213 namedLock.key().resources(),
214 namedLock.key().name());
215 }
216
217 final int attempts = retry + 1;
218 for (int attempt = 1; attempt <= attempts; attempt++) {
219 if (LOGGER.isTraceEnabled()) {
220 LOGGER.trace(
221 "Attempt {}: Acquire {} lock from {}",
222 attempt,
223 lockKind,
224 namedLock.key().name());
225 }
226 try {
227 if (attempt > 1) {
228 Thread.sleep(retryWait);
229 }
230 boolean locked;
231 if (shared) {
232 locked = namedLock.lockShared(time, timeUnit);
233 } else {
234 locked = namedLock.lockExclusively(time, timeUnit);
235 }
236
237 if (locked) {
238
239 locks.push(namedLock);
240 return;
241 }
242
243
244 if (LOGGER.isTraceEnabled()) {
245 LOGGER.trace(
246 "Failed to acquire {} lock for '{}' in {}",
247 lockKind,
248 namedLock.key().name(),
249 timeStr);
250 }
251 } catch (InterruptedException e) {
252
253 close();
254 Thread.currentThread().interrupt();
255 throw new RuntimeException(e);
256 }
257 }
258
259 close();
260 String message = "Could not acquire " + lockKind + " lock for "
261 + lockSubjects(artifacts, metadatas) + " in " + timeStr
262 + "; consider using '" + CONFIG_PROP_TIME
263 + "' property to increase lock timeout to a value that fits your environment";
264 FailedToAcquireLockException ex = new FailedToAcquireLockException(shared, message);
265 throw namedLockFactory.onFailure(ex);
266 }
267
268 private String lockSubjects(
269 Collection<? extends Artifact> artifacts, Collection<? extends Metadata> metadatas) {
270 StringBuilder builder = new StringBuilder();
271 if (artifacts != null && !artifacts.isEmpty()) {
272 builder.append("artifacts: ")
273 .append(artifacts.stream().map(ArtifactIdUtils::toId).collect(Collectors.joining(", ")));
274 }
275 if (metadatas != null && !metadatas.isEmpty()) {
276 if (builder.length() != 0) {
277 builder.append("; ");
278 }
279 builder.append("metadata: ")
280 .append(metadatas.stream().map(this::metadataSubjects).collect(Collectors.joining(", ")));
281 }
282 return builder.toString();
283 }
284
285 private String metadataSubjects(Metadata metadata) {
286 String name = "";
287 if (!metadata.getGroupId().isEmpty()) {
288 name += metadata.getGroupId();
289 if (!metadata.getArtifactId().isEmpty()) {
290 name += ":" + metadata.getArtifactId();
291 if (!metadata.getVersion().isEmpty()) {
292 name += ":" + metadata.getVersion();
293 }
294 }
295 }
296 if (!metadata.getType().isEmpty()) {
297 name += (name.isEmpty() ? "" : ":") + metadata.getType();
298 }
299 return name;
300 }
301
302 @Override
303 public void close() {
304 while (!locks.isEmpty()) {
305 try (NamedLock namedLock = locks.pop()) {
306 namedLock.unlock();
307 if (LOGGER.isTraceEnabled()) {
308 LOGGER.trace(
309 "Unlocked and closed {} lock of {}", shared ? "shared" : "exclusive", namedLock.key());
310 }
311 }
312 }
313 }
314 }
315 }