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