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