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