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  
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              // TODO throw something instead?
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         // 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 }