View Javadoc
1   package org.apache.maven.repository.legacy;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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              // TODO throw something instead?
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         // Here, we need to determine which policy to use. Release updateInterval will be used when
116         // the metadata refers to a release artifact or meta-version, and snapshot updateInterval will be used when
117         // it refers to a snapshot artifact or meta-version.
118         // NOTE: Release metadata includes version information about artifacts that have been released, to allow
119         // meta-versions like RELEASE and LATEST to resolve, and also to allow retrieval of the range of valid, released
120         // artifacts available.
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             // TODO throw something instead?
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         // consider the username&password because a repo manager might block artifacts depending on authorization
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         // consider the URL (instead of the id) as this most closely relates to the contents in the repo
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 }