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