1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.repository.legacy;
20
21 import javax.inject.Named;
22 import javax.inject.Singleton;
23
24 import java.io.File;
25 import java.io.FileInputStream;
26 import java.io.IOException;
27 import java.io.RandomAccessFile;
28 import java.nio.channels.Channels;
29 import java.nio.channels.FileChannel;
30 import java.nio.channels.FileLock;
31 import java.util.Date;
32 import java.util.Properties;
33
34 import org.apache.maven.artifact.Artifact;
35 import org.apache.maven.artifact.repository.ArtifactRepository;
36 import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
37 import org.apache.maven.artifact.repository.Authentication;
38 import org.apache.maven.artifact.repository.metadata.RepositoryMetadata;
39 import org.apache.maven.repository.Proxy;
40 import org.codehaus.plexus.logging.AbstractLogEnabled;
41 import org.codehaus.plexus.logging.Logger;
42
43
44
45
46 @Named
47 @Singleton
48 public class DefaultUpdateCheckManager extends AbstractLogEnabled implements UpdateCheckManager {
49
50 private static final String ERROR_KEY_SUFFIX = ".error";
51
52 public DefaultUpdateCheckManager() {}
53
54 public DefaultUpdateCheckManager(Logger logger) {
55 enableLogging(logger);
56 }
57
58 public static final String LAST_UPDATE_TAG = ".lastUpdated";
59
60 private static final String TOUCHFILE_NAME = "resolver-status.properties";
61
62 public boolean isUpdateRequired(Artifact artifact, ArtifactRepository repository) {
63 File file = artifact.getFile();
64
65 ArtifactRepositoryPolicy policy = artifact.isSnapshot() ? repository.getSnapshots() : repository.getReleases();
66
67 if (!policy.isEnabled()) {
68 if (getLogger().isDebugEnabled()) {
69 getLogger()
70 .debug("Skipping update check for " + artifact + " (" + file + ") from " + repository.getId()
71 + " (" + repository.getUrl() + ")");
72 }
73
74 return false;
75 }
76
77 if (getLogger().isDebugEnabled()) {
78 getLogger()
79 .debug("Determining update check for " + artifact + " (" + file + ") from " + repository.getId()
80 + " (" + repository.getUrl() + ")");
81 }
82
83 if (file == null) {
84
85 return true;
86 }
87
88 Date lastCheckDate;
89
90 if (file.exists()) {
91 lastCheckDate = new Date(file.lastModified());
92 } else {
93 File touchfile = getTouchfile(artifact);
94 lastCheckDate = readLastUpdated(touchfile, getRepositoryKey(repository));
95 }
96
97 return (lastCheckDate == null) || policy.checkOutOfDate(lastCheckDate);
98 }
99
100 public boolean isUpdateRequired(RepositoryMetadata metadata, ArtifactRepository repository, File file) {
101
102
103
104
105
106
107 ArtifactRepositoryPolicy policy = metadata.getPolicy(repository);
108
109 if (!policy.isEnabled()) {
110 if (getLogger().isDebugEnabled()) {
111 getLogger()
112 .debug("Skipping update check for " + metadata.getKey() + " (" + file + ") from "
113 + repository.getId() + " (" + repository.getUrl() + ")");
114 }
115
116 return false;
117 }
118
119 if (getLogger().isDebugEnabled()) {
120 getLogger()
121 .debug("Determining update check for " + metadata.getKey() + " (" + file + ") from "
122 + repository.getId() + " (" + repository.getUrl() + ")");
123 }
124
125 if (file == null) {
126
127 return true;
128 }
129
130 Date lastCheckDate = readLastUpdated(metadata, repository, file);
131
132 return (lastCheckDate == null) || policy.checkOutOfDate(lastCheckDate);
133 }
134
135 private Date readLastUpdated(RepositoryMetadata metadata, ArtifactRepository repository, File file) {
136 File touchfile = getTouchfile(metadata, file);
137
138 String key = getMetadataKey(repository, file);
139
140 return readLastUpdated(touchfile, key);
141 }
142
143 public String getError(Artifact artifact, ArtifactRepository repository) {
144 File touchFile = getTouchfile(artifact);
145 return getError(touchFile, getRepositoryKey(repository));
146 }
147
148 public void touch(Artifact artifact, ArtifactRepository repository, String error) {
149 File file = artifact.getFile();
150
151 File touchfile = getTouchfile(artifact);
152
153 if (file.exists()) {
154 touchfile.delete();
155 } else {
156 writeLastUpdated(touchfile, getRepositoryKey(repository), error);
157 }
158 }
159
160 public void touch(RepositoryMetadata metadata, ArtifactRepository repository, File file) {
161 File touchfile = getTouchfile(metadata, file);
162
163 String key = getMetadataKey(repository, file);
164
165 writeLastUpdated(touchfile, key, null);
166 }
167
168 String getMetadataKey(ArtifactRepository repository, File file) {
169 return repository.getId() + '.' + file.getName() + LAST_UPDATE_TAG;
170 }
171
172 String getRepositoryKey(ArtifactRepository repository) {
173 StringBuilder buffer = new StringBuilder(256);
174
175 Proxy proxy = repository.getProxy();
176 if (proxy != null) {
177 if (proxy.getUserName() != null) {
178 int hash = (proxy.getUserName() + proxy.getPassword()).hashCode();
179 buffer.append(hash).append('@');
180 }
181 buffer.append(proxy.getHost()).append(':').append(proxy.getPort()).append('>');
182 }
183
184
185 Authentication auth = repository.getAuthentication();
186 if (auth != null) {
187 int hash = (auth.getUsername() + auth.getPassword()).hashCode();
188 buffer.append(hash).append('@');
189 }
190
191
192 buffer.append(repository.getUrl());
193
194 return buffer.toString();
195 }
196
197 private void writeLastUpdated(File touchfile, String key, String error) {
198 synchronized (touchfile.getAbsolutePath().intern()) {
199 if (!touchfile.getParentFile().exists()
200 && !touchfile.getParentFile().mkdirs()) {
201 getLogger()
202 .debug("Failed to create directory: " + touchfile.getParent()
203 + " for tracking artifact metadata resolution.");
204 return;
205 }
206
207 FileChannel channel = null;
208 FileLock lock = null;
209 try {
210 Properties props = new Properties();
211
212 channel = new RandomAccessFile(touchfile, "rw").getChannel();
213 lock = channel.lock();
214
215 if (touchfile.canRead()) {
216 getLogger().debug("Reading resolution-state from: " + touchfile);
217 props.load(Channels.newInputStream(channel));
218 }
219
220 props.setProperty(key, Long.toString(System.currentTimeMillis()));
221
222 if (error != null) {
223 props.setProperty(key + ERROR_KEY_SUFFIX, error);
224 } else {
225 props.remove(key + ERROR_KEY_SUFFIX);
226 }
227
228 getLogger().debug("Writing resolution-state to: " + touchfile);
229 channel.truncate(0);
230 props.store(Channels.newOutputStream(channel), "Last modified on: " + new Date());
231
232 lock.release();
233 lock = null;
234
235 channel.close();
236 channel = null;
237 } catch (IOException e) {
238 getLogger()
239 .debug(
240 "Failed to record lastUpdated information for resolution.\nFile: "
241 + touchfile.toString() + "; key: " + key,
242 e);
243 } finally {
244 if (lock != null) {
245 try {
246 lock.release();
247 } catch (IOException e) {
248 getLogger()
249 .debug("Error releasing exclusive lock for resolution tracking file: " + touchfile, e);
250 }
251 }
252
253 if (channel != null) {
254 try {
255 channel.close();
256 } catch (IOException e) {
257 getLogger().debug("Error closing FileChannel for resolution tracking file: " + touchfile, e);
258 }
259 }
260 }
261 }
262 }
263
264 Date readLastUpdated(File touchfile, String key) {
265 getLogger().debug("Searching for " + key + " in resolution tracking file.");
266
267 Properties props = read(touchfile);
268 if (props != null) {
269 String rawVal = props.getProperty(key);
270 if (rawVal != null) {
271 try {
272 return new Date(Long.parseLong(rawVal));
273 } catch (NumberFormatException e) {
274 getLogger().debug("Cannot parse lastUpdated date: '" + rawVal + "'. Ignoring.", e);
275 }
276 }
277 }
278 return null;
279 }
280
281 private String getError(File touchFile, String key) {
282 Properties props = read(touchFile);
283 if (props != null) {
284 return props.getProperty(key + ERROR_KEY_SUFFIX);
285 }
286 return null;
287 }
288
289 private Properties read(File touchfile) {
290 if (!touchfile.canRead()) {
291 getLogger().debug("Skipped unreadable resolution tracking file: " + touchfile);
292 return null;
293 }
294
295 synchronized (touchfile.getAbsolutePath().intern()) {
296 try {
297 Properties props = new Properties();
298
299 try (FileInputStream in = new FileInputStream(touchfile)) {
300 try (FileLock lock = in.getChannel().lock(0, Long.MAX_VALUE, true)) {
301 getLogger().debug("Reading resolution-state from: " + touchfile);
302 props.load(in);
303
304 return props;
305 }
306 }
307
308 } catch (IOException e) {
309 getLogger().debug("Failed to read resolution tracking file: " + touchfile, e);
310
311 return null;
312 }
313 }
314 }
315
316 File getTouchfile(Artifact artifact) {
317 StringBuilder sb = new StringBuilder(128);
318 sb.append(artifact.getArtifactId());
319 sb.append('-').append(artifact.getBaseVersion());
320 if (artifact.getClassifier() != null) {
321 sb.append('-').append(artifact.getClassifier());
322 }
323 sb.append('.').append(artifact.getType()).append(LAST_UPDATE_TAG);
324 return new File(artifact.getFile().getParentFile(), sb.toString());
325 }
326
327 File getTouchfile(RepositoryMetadata metadata, File file) {
328 return new File(file.getParent(), TOUCHFILE_NAME);
329 }
330 }