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;
20
21 import java.io.ByteArrayOutputStream;
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.UncheckedIOException;
25 import java.nio.ByteBuffer;
26 import java.nio.channels.Channels;
27 import java.nio.channels.FileChannel;
28 import java.nio.channels.FileLock;
29 import java.nio.channels.OverlappingFileLockException;
30 import java.nio.file.Files;
31 import java.nio.file.NoSuchFileException;
32 import java.nio.file.Path;
33 import java.nio.file.StandardOpenOption;
34 import java.util.Map;
35 import java.util.Properties;
36
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 public final class LegacyTrackingFileManager implements TrackingFileManager {
56 private static final Logger LOGGER = LoggerFactory.getLogger(LegacyTrackingFileManager.class);
57
58 @Deprecated
59 @Override
60 public Properties read(File file) {
61 return read(file.toPath());
62 }
63
64 @Override
65 public Properties read(Path path) {
66 if (Files.isReadable(path)) {
67 synchronized (mutex(path)) {
68 try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ);
69 FileLock unused = fileLock(fileChannel, true)) {
70 Properties props = new Properties();
71 props.load(Channels.newInputStream(fileChannel));
72 return props;
73 } catch (NoSuchFileException e) {
74 LOGGER.debug("No such file to read {}: {}", path, e.getMessage());
75 } catch (IOException e) {
76 LOGGER.warn("Failed to read tracking file '{}'", path, e);
77 throw new UncheckedIOException(e);
78 }
79 }
80 }
81 return null;
82 }
83
84 @Deprecated
85 @Override
86 public Properties update(File file, Map<String, String> updates) {
87 return update(file.toPath(), updates);
88 }
89
90 @Override
91 public Properties update(Path path, Map<String, String> updates) {
92 try {
93 Files.createDirectories(path.getParent());
94 } catch (IOException e) {
95 LOGGER.warn("Failed to create tracking file parent '{}'", path, e);
96 throw new UncheckedIOException(e);
97 }
98 synchronized (mutex(path)) {
99 try (FileChannel fileChannel = FileChannel.open(
100 path, StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
101 FileLock unused = fileLock(fileChannel, false)) {
102 Properties props = new Properties();
103 if (fileChannel.size() > 0) {
104 props.load(Channels.newInputStream(fileChannel));
105 }
106
107 for (Map.Entry<String, String> update : updates.entrySet()) {
108 if (update.getValue() == null) {
109 props.remove(update.getKey());
110 } else {
111 props.setProperty(update.getKey(), update.getValue());
112 }
113 }
114
115 LOGGER.debug("Writing tracking file '{}'", path);
116 ByteArrayOutputStream stream = new ByteArrayOutputStream(1024 * 2);
117 props.store(
118 stream,
119 "NOTE: This is a Maven Resolver internal implementation file"
120 + ", its format can be changed without prior notice.");
121 fileChannel.position(0);
122 int written = fileChannel.write(ByteBuffer.wrap(stream.toByteArray()));
123 fileChannel.truncate(written);
124 return props;
125 } catch (IOException e) {
126 LOGGER.warn("Failed to write tracking file '{}'", path, e);
127 throw new UncheckedIOException(e);
128 }
129 }
130 }
131
132 @Deprecated
133 @Override
134 public boolean delete(File file) {
135 return delete(file.toPath());
136 }
137
138 @Override
139 public boolean delete(Path path) {
140 if (Files.isReadable(path)) {
141 synchronized (mutex(path)) {
142 try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.WRITE);
143 FileLock unused = fileLock(fileChannel, false)) {
144 Files.delete(path);
145 return true;
146 } catch (NoSuchFileException e) {
147 LOGGER.debug("No such file to delete {}: {}", path, e.getMessage());
148 } catch (IOException e) {
149 LOGGER.warn("Failed to delete tracking file '{}'", path, e);
150 throw new UncheckedIOException(e);
151 }
152 }
153 }
154 return false;
155 }
156
157
158
159
160
161
162
163 private static Object mutex(Path path) {
164
165
166 return canonicalPath(path).toString().intern();
167 }
168
169
170
171
172
173 private static Path canonicalPath(Path path) {
174 try {
175 return path.toRealPath();
176 } catch (IOException e) {
177 return canonicalPath(path.getParent()).resolve(path.getFileName());
178 }
179 }
180
181 private FileLock fileLock(FileChannel channel, boolean shared) throws IOException {
182 FileLock lock = null;
183 for (int attempts = 8; attempts >= 0; attempts--) {
184 try {
185 lock = channel.lock(0, Long.MAX_VALUE, shared);
186 break;
187 } catch (OverlappingFileLockException | IOException e) {
188
189
190
191
192
193
194
195
196
197
198
199 if (attempts <= 0) {
200 throw (e instanceof IOException) ? (IOException) e : new IOException(e);
201 }
202 try {
203 Thread.sleep(50L);
204 } catch (InterruptedException e1) {
205 Thread.currentThread().interrupt();
206 }
207 }
208 }
209 if (lock == null) {
210 throw new IOException("Could not lock file");
211 }
212 return lock;
213 }
214 }