View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.repository.legacy;
20  
21  import javax.inject.Named;
22  import javax.inject.Singleton;
23  
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.io.IOException;
27  import java.io.RandomAccessFile;
28  import java.nio.channels.Channels;
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.logging.AbstractLogEnabled;
41  import org.codehaus.plexus.logging.Logger;
42  
43  /**
44   * DefaultUpdateCheckManager
45   */
46  @Named
47  @Singleton
48  @Deprecated
49  public class DefaultUpdateCheckManager extends AbstractLogEnabled implements UpdateCheckManager {
50  
51      private static final String ERROR_KEY_SUFFIX = ".error";
52  
53      public DefaultUpdateCheckManager() {}
54  
55      public DefaultUpdateCheckManager(Logger logger) {
56          enableLogging(logger);
57      }
58  
59      public static final String LAST_UPDATE_TAG = ".lastUpdated";
60  
61      private static final String TOUCHFILE_NAME = "resolver-status.properties";
62  
63      public boolean isUpdateRequired(Artifact artifact, ArtifactRepository repository) {
64          File file = artifact.getFile();
65  
66          ArtifactRepositoryPolicy policy = artifact.isSnapshot() ? repository.getSnapshots() : repository.getReleases();
67  
68          if (!policy.isEnabled()) {
69              if (getLogger().isDebugEnabled()) {
70                  getLogger()
71                          .debug("Skipping update check for " + artifact + " (" + file + ") from " + repository.getId()
72                                  + " (" + repository.getUrl() + ")");
73              }
74  
75              return false;
76          }
77  
78          if (getLogger().isDebugEnabled()) {
79              getLogger()
80                      .debug("Determining update check for " + artifact + " (" + file + ") from " + repository.getId()
81                              + " (" + repository.getUrl() + ")");
82          }
83  
84          if (file == null) {
85              // TODO throw something instead?
86              return true;
87          }
88  
89          Date lastCheckDate;
90  
91          if (file.exists()) {
92              lastCheckDate = new Date(file.lastModified());
93          } else {
94              File touchfile = getTouchfile(artifact);
95              lastCheckDate = readLastUpdated(touchfile, getRepositoryKey(repository));
96          }
97  
98          return (lastCheckDate == null) || policy.checkOutOfDate(lastCheckDate);
99      }
100 
101     public boolean isUpdateRequired(RepositoryMetadata metadata, ArtifactRepository repository, File file) {
102         // Here, we need to determine which policy to use. Release updateInterval will be used when
103         // the metadata refers to a release artifact or meta-version, and snapshot updateInterval will be used when
104         // it refers to a snapshot artifact or meta-version.
105         // NOTE: Release metadata includes version information about artifacts that have been released, to allow
106         // meta-versions like RELEASE and LATEST to resolve, and also to allow retrieval of the range of valid, released
107         // artifacts available.
108         ArtifactRepositoryPolicy policy = metadata.getPolicy(repository);
109 
110         if (!policy.isEnabled()) {
111             if (getLogger().isDebugEnabled()) {
112                 getLogger()
113                         .debug("Skipping update check for " + metadata.getKey() + " (" + file + ") from "
114                                 + repository.getId() + " (" + repository.getUrl() + ")");
115             }
116 
117             return false;
118         }
119 
120         if (getLogger().isDebugEnabled()) {
121             getLogger()
122                     .debug("Determining update check for " + metadata.getKey() + " (" + file + ") from "
123                             + repository.getId() + " (" + repository.getUrl() + ")");
124         }
125 
126         if (file == null) {
127             // TODO throw something instead?
128             return true;
129         }
130 
131         Date lastCheckDate = readLastUpdated(metadata, repository, file);
132 
133         return (lastCheckDate == null) || policy.checkOutOfDate(lastCheckDate);
134     }
135 
136     private Date readLastUpdated(RepositoryMetadata metadata, ArtifactRepository repository, File file) {
137         File touchfile = getTouchfile(metadata, file);
138 
139         String key = getMetadataKey(repository, file);
140 
141         return readLastUpdated(touchfile, key);
142     }
143 
144     public String getError(Artifact artifact, ArtifactRepository repository) {
145         File touchFile = getTouchfile(artifact);
146         return getError(touchFile, getRepositoryKey(repository));
147     }
148 
149     public void touch(Artifact artifact, ArtifactRepository repository, String error) {
150         File file = artifact.getFile();
151 
152         File touchfile = getTouchfile(artifact);
153 
154         if (file.exists()) {
155             touchfile.delete();
156         } else {
157             writeLastUpdated(touchfile, getRepositoryKey(repository), error);
158         }
159     }
160 
161     public void touch(RepositoryMetadata metadata, ArtifactRepository repository, File file) {
162         File touchfile = getTouchfile(metadata, file);
163 
164         String key = getMetadataKey(repository, file);
165 
166         writeLastUpdated(touchfile, key, null);
167     }
168 
169     String getMetadataKey(ArtifactRepository repository, File file) {
170         return repository.getId() + '.' + file.getName() + LAST_UPDATE_TAG;
171     }
172 
173     String getRepositoryKey(ArtifactRepository repository) {
174         StringBuilder buffer = new StringBuilder(256);
175 
176         Proxy proxy = repository.getProxy();
177         if (proxy != null) {
178             if (proxy.getUserName() != null) {
179                 int hash = (proxy.getUserName() + proxy.getPassword()).hashCode();
180                 buffer.append(hash).append('@');
181             }
182             buffer.append(proxy.getHost()).append(':').append(proxy.getPort()).append('>');
183         }
184 
185         // consider the username&password because a repo manager might block artifacts depending on authorization
186         Authentication auth = repository.getAuthentication();
187         if (auth != null) {
188             int hash = (auth.getUsername() + auth.getPassword()).hashCode();
189             buffer.append(hash).append('@');
190         }
191 
192         // consider the URL (instead of the id) as this most closely relates to the contents in the repo
193         buffer.append(repository.getUrl());
194 
195         return buffer.toString();
196     }
197 
198     private void writeLastUpdated(File touchfile, String key, String error) {
199         synchronized (touchfile.getAbsolutePath().intern()) {
200             if (!touchfile.getParentFile().exists()
201                     && !touchfile.getParentFile().mkdirs()) {
202                 getLogger()
203                         .debug("Failed to create directory: " + touchfile.getParent()
204                                 + " for tracking artifact metadata resolution.");
205                 return;
206             }
207 
208             FileChannel channel = null;
209             FileLock lock = null;
210             try {
211                 Properties props = new Properties();
212 
213                 channel = new RandomAccessFile(touchfile, "rw").getChannel();
214                 lock = channel.lock();
215 
216                 if (touchfile.canRead()) {
217                     getLogger().debug("Reading resolution-state from: " + touchfile);
218                     props.load(Channels.newInputStream(channel));
219                 }
220 
221                 props.setProperty(key, Long.toString(System.currentTimeMillis()));
222 
223                 if (error != null) {
224                     props.setProperty(key + ERROR_KEY_SUFFIX, error);
225                 } else {
226                     props.remove(key + ERROR_KEY_SUFFIX);
227                 }
228 
229                 getLogger().debug("Writing resolution-state to: " + touchfile);
230                 channel.truncate(0);
231                 props.store(Channels.newOutputStream(channel), "Last modified on: " + new Date());
232 
233                 lock.release();
234                 lock = null;
235 
236                 channel.close();
237                 channel = null;
238             } catch (IOException e) {
239                 getLogger()
240                         .debug(
241                                 "Failed to record lastUpdated information for resolution.\nFile: "
242                                         + touchfile.toString() + "; key: " + key,
243                                 e);
244             } finally {
245                 if (lock != null) {
246                     try {
247                         lock.release();
248                     } catch (IOException e) {
249                         getLogger()
250                                 .debug("Error releasing exclusive lock for resolution tracking file: " + touchfile, e);
251                     }
252                 }
253 
254                 if (channel != null) {
255                     try {
256                         channel.close();
257                     } catch (IOException e) {
258                         getLogger().debug("Error closing FileChannel for resolution tracking file: " + touchfile, e);
259                     }
260                 }
261             }
262         }
263     }
264 
265     Date readLastUpdated(File touchfile, String key) {
266         getLogger().debug("Searching for " + key + " in resolution tracking file.");
267 
268         Properties props = read(touchfile);
269         if (props != null) {
270             String rawVal = props.getProperty(key);
271             if (rawVal != null) {
272                 try {
273                     return new Date(Long.parseLong(rawVal));
274                 } catch (NumberFormatException e) {
275                     getLogger().debug("Cannot parse lastUpdated date: '" + rawVal + "'. Ignoring.", e);
276                 }
277             }
278         }
279         return null;
280     }
281 
282     private String getError(File touchFile, String key) {
283         Properties props = read(touchFile);
284         if (props != null) {
285             return props.getProperty(key + ERROR_KEY_SUFFIX);
286         }
287         return null;
288     }
289 
290     private Properties read(File touchfile) {
291         if (!touchfile.canRead()) {
292             getLogger().debug("Skipped unreadable resolution tracking file: " + touchfile);
293             return null;
294         }
295 
296         synchronized (touchfile.getAbsolutePath().intern()) {
297             try {
298                 Properties props = new Properties();
299 
300                 try (FileInputStream in = new FileInputStream(touchfile)) {
301                     try (FileLock lock = in.getChannel().lock(0, Long.MAX_VALUE, true)) {
302                         getLogger().debug("Reading resolution-state from: " + touchfile);
303                         props.load(in);
304 
305                         return props;
306                     }
307                 }
308 
309             } catch (IOException e) {
310                 getLogger().debug("Failed to read resolution tracking file: " + touchfile, e);
311 
312                 return null;
313             }
314         }
315     }
316 
317     File getTouchfile(Artifact artifact) {
318         StringBuilder sb = new StringBuilder(128);
319         sb.append(artifact.getArtifactId());
320         sb.append('-').append(artifact.getBaseVersion());
321         if (artifact.getClassifier() != null) {
322             sb.append('-').append(artifact.getClassifier());
323         }
324         sb.append('.').append(artifact.getType()).append(LAST_UPDATE_TAG);
325         return new File(artifact.getFile().getParentFile(), sb.toString());
326     }
327 
328     File getTouchfile(RepositoryMetadata metadata, File file) {
329         return new File(file.getParent(), TOUCHFILE_NAME);
330     }
331 }