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