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