001 package org.apache.maven.repository.legacy;
002
003 /*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements. See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership. The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License. You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied. See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022 import java.io.ByteArrayInputStream;
023 import java.io.ByteArrayOutputStream;
024 import java.io.File;
025 import java.io.FileInputStream;
026 import java.io.IOException;
027 import java.io.RandomAccessFile;
028 import java.nio.ByteBuffer;
029 import java.nio.channels.FileChannel;
030 import java.nio.channels.FileLock;
031 import java.util.Date;
032 import java.util.Properties;
033
034 import org.apache.maven.artifact.Artifact;
035 import org.apache.maven.artifact.repository.ArtifactRepository;
036 import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
037 import org.apache.maven.artifact.repository.Authentication;
038 import org.apache.maven.artifact.repository.metadata.RepositoryMetadata;
039 import org.apache.maven.repository.Proxy;
040 import org.codehaus.plexus.component.annotations.Component;
041 import org.codehaus.plexus.logging.AbstractLogEnabled;
042 import org.codehaus.plexus.logging.Logger;
043
044 @Component( role = UpdateCheckManager.class )
045 public class DefaultUpdateCheckManager
046 extends AbstractLogEnabled
047 implements UpdateCheckManager
048 {
049
050 private static final String ERROR_KEY_SUFFIX = ".error";
051
052 public DefaultUpdateCheckManager()
053 {
054
055 }
056
057 public DefaultUpdateCheckManager( Logger logger )
058 {
059 enableLogging( logger );
060 }
061
062 public static final String LAST_UPDATE_TAG = ".lastUpdated";
063
064 private static final String TOUCHFILE_NAME = "resolver-status.properties";
065
066 public boolean isUpdateRequired( Artifact artifact, ArtifactRepository repository )
067 {
068 File file = artifact.getFile();
069
070 ArtifactRepositoryPolicy policy = artifact.isSnapshot() ? repository.getSnapshots() : repository.getReleases();
071
072 if ( !policy.isEnabled() )
073 {
074 if ( getLogger().isDebugEnabled() )
075 {
076 getLogger().debug(
077 "Skipping update check for " + artifact + " (" + file + ") from "
078 + repository.getId() + " (" + repository.getUrl() + ")" );
079 }
080
081 return false;
082 }
083
084 if ( getLogger().isDebugEnabled() )
085 {
086 getLogger().debug(
087 "Determining update check for " + artifact + " (" + file + ") from "
088 + repository.getId() + " (" + repository.getUrl() + ")" );
089 }
090
091 if ( file == null )
092 {
093 // TODO throw something instead?
094 return true;
095 }
096
097 Date lastCheckDate;
098
099 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 // Here, we need to determine which policy to use. Release updateInterval will be used when
115 // the metadata refers to a release artifact or meta-version, and snapshot updateInterval will be used when
116 // it refers to a snapshot artifact or meta-version.
117 // NOTE: Release metadata includes version information about artifacts that have been released, to allow
118 // meta-versions like RELEASE and LATEST to resolve, and also to allow retrieval of the range of valid, released
119 // artifacts available.
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 // TODO throw something instead?
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 // consider the username&password because a repo manager might block artifacts depending on authorization
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 // consider the URL (instead of the id) as this most closely relates to the contents in the repo
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 }