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