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