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.util.Map;
36 import java.util.Properties;
37
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41
42
43
44
45
46
47
48
49 @Singleton
50 @Named
51 public final class DefaultTrackingFileManager implements TrackingFileManager {
52 private static final Logger LOGGER = LoggerFactory.getLogger(DefaultTrackingFileManager.class);
53
54 @Override
55 public Properties read(File file) {
56 if (Files.isReadable(file.toPath())) {
57 synchronized (getMutex(file)) {
58 try (FileInputStream stream = new FileInputStream(file);
59 FileLock unused = fileLock(stream.getChannel(), Math.max(1, file.length()), true)) {
60 Properties props = new Properties();
61 props.load(stream);
62 return props;
63 } catch (IOException e) {
64 LOGGER.warn("Failed to read tracking file '{}'", file, e);
65 throw new UncheckedIOException(e);
66 }
67 }
68 }
69 return null;
70 }
71
72 @Override
73 public Properties update(File file, Map<String, String> updates) {
74 Properties props = new Properties();
75
76 try {
77 Files.createDirectories(file.getParentFile().toPath());
78 } catch (IOException e) {
79 LOGGER.warn("Failed to create tracking file parent '{}'", file, e);
80 throw new UncheckedIOException(e);
81 }
82
83 synchronized (getMutex(file)) {
84 try (RandomAccessFile raf = new RandomAccessFile(file, "rw");
85 FileLock unused = fileLock(raf.getChannel(), Math.max(1, raf.length()), false)) {
86 if (raf.length() > 0) {
87 byte[] buffer = new byte[(int) raf.length()];
88 raf.readFully(buffer);
89 props.load(new ByteArrayInputStream(buffer));
90 }
91
92 for (Map.Entry<String, String> update : updates.entrySet()) {
93 if (update.getValue() == null) {
94 props.remove(update.getKey());
95 } else {
96 props.setProperty(update.getKey(), update.getValue());
97 }
98 }
99
100 LOGGER.debug("Writing tracking file '{}'", file);
101 ByteArrayOutputStream stream = new ByteArrayOutputStream(1024 * 2);
102 props.store(
103 stream,
104 "NOTE: This is a Maven Resolver internal implementation file"
105 + ", its format can be changed without prior notice.");
106 raf.seek(0L);
107 raf.write(stream.toByteArray());
108 raf.setLength(raf.getFilePointer());
109 } catch (IOException e) {
110 LOGGER.warn("Failed to write tracking file '{}'", file, e);
111 throw new UncheckedIOException(e);
112 }
113 }
114
115 return props;
116 }
117
118 private Object getMutex(File file) {
119
120
121
122
123
124
125
126 try {
127 return file.getCanonicalPath().intern();
128 } catch (IOException e) {
129 LOGGER.warn("Failed to canonicalize path {}", file, e);
130
131 return file.getAbsolutePath().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 }