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.ByteArrayInputStream;
25 import java.io.ByteArrayOutputStream;
26 import java.io.File;
27 import java.io.FileInputStream;
28 import java.io.IOException;
29 import java.io.RandomAccessFile;
30 import java.io.UncheckedIOException;
31 import java.nio.channels.FileChannel;
32 import java.nio.channels.FileLock;
33 import java.nio.channels.OverlappingFileLockException;
34 import java.nio.file.Files;
35 import java.nio.file.Path;
36 import java.util.Map;
37 import java.util.Properties;
38
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42
43
44
45
46
47
48
49
50 @Singleton
51 @Named
52 public final class DefaultTrackingFileManager implements TrackingFileManager {
53 private static final Logger LOGGER = LoggerFactory.getLogger(DefaultTrackingFileManager.class);
54
55 @Override
56 public Properties read(File file) {
57 Path filePath = file.toPath();
58 if (Files.isReadable(filePath)) {
59 synchronized (getMutex(filePath)) {
60 try (FileInputStream stream = new FileInputStream(filePath.toFile());
61 FileLock unused = fileLock(stream.getChannel(), Math.max(1, file.length()), true)) {
62 Properties props = new Properties();
63 props.load(stream);
64 return props;
65 } catch (IOException e) {
66 LOGGER.warn("Failed to read tracking file '{}'", file, e);
67 throw new UncheckedIOException(e);
68 }
69 }
70 }
71 return null;
72 }
73
74 @Override
75 public Properties update(File file, Map<String, String> updates) {
76 Path filePath = file.toPath();
77 Properties props = new Properties();
78
79 try {
80 Files.createDirectories(filePath.getParent());
81 } catch (IOException e) {
82 LOGGER.warn("Failed to create tracking file parent '{}'", file, e);
83 throw new UncheckedIOException(e);
84 }
85
86 synchronized (getMutex(filePath)) {
87 try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "rw");
88 FileLock unused = fileLock(raf.getChannel(), Math.max(1, raf.length()), false)) {
89 if (raf.length() > 0) {
90 byte[] buffer = new byte[(int) raf.length()];
91 raf.readFully(buffer);
92 props.load(new ByteArrayInputStream(buffer));
93 }
94
95 for (Map.Entry<String, String> update : updates.entrySet()) {
96 if (update.getValue() == null) {
97 props.remove(update.getKey());
98 } else {
99 props.setProperty(update.getKey(), update.getValue());
100 }
101 }
102
103 LOGGER.debug("Writing tracking file '{}'", file);
104 ByteArrayOutputStream stream = new ByteArrayOutputStream(1024 * 2);
105 props.store(
106 stream,
107 "NOTE: This is a Maven Resolver internal implementation file"
108 + ", its format can be changed without prior notice.");
109 raf.seek(0L);
110 raf.write(stream.toByteArray());
111 raf.setLength(raf.getFilePointer());
112 } catch (IOException e) {
113 LOGGER.warn("Failed to write tracking file '{}'", file, e);
114 throw new UncheckedIOException(e);
115 }
116 }
117
118 return props;
119 }
120
121 private Object getMutex(Path file) {
122
123
124
125
126
127 try {
128 return file.toRealPath().toString().intern();
129 } catch (IOException e) {
130 LOGGER.warn("Failed to get real path {}", file, e);
131 return file.toAbsolutePath().toString().intern();
132 }
133 }
134
135 @SuppressWarnings({"checkstyle:magicnumber"})
136 private FileLock fileLock(FileChannel channel, long size, boolean shared) throws IOException {
137 FileLock lock = null;
138 for (int attempts = 8; attempts >= 0; attempts--) {
139 try {
140 lock = channel.lock(0, size, shared);
141 break;
142 } catch (OverlappingFileLockException e) {
143 if (attempts <= 0) {
144 throw new IOException(e);
145 }
146 try {
147 Thread.sleep(50L);
148 } catch (InterruptedException e1) {
149 Thread.currentThread().interrupt();
150 }
151 }
152 }
153 if (lock == null) {
154 throw new IOException("Could not lock file");
155 }
156 return lock;
157 }
158 }