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      @Override
64      public boolean isUpdateRequired(Artifact artifact, ArtifactRepository repository) {
65          File file = artifact.getFile();
66  
67          ArtifactRepositoryPolicy policy = artifact.isSnapshot() ? repository.getSnapshots() : repository.getReleases();
68  
69          if (!policy.isEnabled()) {
70              if (getLogger().isDebugEnabled()) {
71                  getLogger()
72                          .debug("Skipping update check for " + artifact + " (" + file + ") from " + repository.getId()
73                                  + " (" + repository.getUrl() + ")");
74              }
75  
76              return false;
77          }
78  
79          if (getLogger().isDebugEnabled()) {
80              getLogger()
81                      .debug("Determining update check for " + artifact + " (" + file + ") from " + repository.getId()
82                              + " (" + repository.getUrl() + ")");
83          }
84  
85          if (file == null) {
86              // TODO throw something instead?
87              return true;
88          }
89  
90          Date lastCheckDate;
91  
92          if (file.exists()) {
93              lastCheckDate = new Date(file.lastModified());
94          } else {
95              File touchfile = getTouchfile(artifact);
96              lastCheckDate = readLastUpdated(touchfile, getRepositoryKey(repository));
97          }
98  
99          return (lastCheckDate == null) || policy.checkOutOfDate(lastCheckDate);
100     }
101 
102     @Override
103     public boolean isUpdateRequired(RepositoryMetadata metadata, ArtifactRepository repository, File file) {
104         // Here, we need to determine which policy to use. Release updateInterval will be used when
105         // the metadata refers to a release artifact or meta-version, and snapshot updateInterval will be used when
106         // it refers to a snapshot artifact or meta-version.
107         // NOTE: Release metadata includes version information about artifacts that have been released, to allow
108         // meta-versions like RELEASE and LATEST to resolve, and also to allow retrieval of the range of valid, released
109         // artifacts available.
110         ArtifactRepositoryPolicy policy = metadata.getPolicy(repository);
111 
112         if (!policy.isEnabled()) {
113             if (getLogger().isDebugEnabled()) {
114                 getLogger()
115                         .debug("Skipping update check for " + metadata.getKey() + " (" + file + ") from "
116                                 + repository.getId() + " (" + repository.getUrl() + ")");
117             }
118 
119             return false;
120         }
121 
122         if (getLogger().isDebugEnabled()) {
123             getLogger()
124                     .debug("Determining update check for " + metadata.getKey() + " (" + file + ") from "
125                             + repository.getId() + " (" + repository.getUrl() + ")");
126         }
127 
128         if (file == null) {
129             // TODO throw something instead?
130             return true;
131         }
132 
133         Date lastCheckDate = readLastUpdated(metadata, repository, file);
134 
135         return (lastCheckDate == null) || policy.checkOutOfDate(lastCheckDate);
136     }
137 
138     private Date readLastUpdated(RepositoryMetadata metadata, ArtifactRepository repository, File file) {
139         File touchfile = getTouchfile(metadata, file);
140 
141         String key = getMetadataKey(repository, file);
142 
143         return readLastUpdated(touchfile, key);
144     }
145 
146     @Override
147     public String getError(Artifact artifact, ArtifactRepository repository) {
148         File touchFile = getTouchfile(artifact);
149         return getError(touchFile, getRepositoryKey(repository));
150     }
151 
152     @Override
153     public void touch(Artifact artifact, ArtifactRepository repository, String error) {
154         File file = artifact.getFile();
155 
156         File touchfile = getTouchfile(artifact);
157 
158         if (file.exists()) {
159             touchfile.delete();
160         } else {
161             writeLastUpdated(touchfile, getRepositoryKey(repository), error);
162         }
163     }
164 
165     @Override
166     public void touch(RepositoryMetadata metadata, ArtifactRepository repository, File file) {
167         File touchfile = getTouchfile(metadata, file);
168 
169         String key = getMetadataKey(repository, file);
170 
171         writeLastUpdated(touchfile, key, null);
172     }
173 
174     String getMetadataKey(ArtifactRepository repository, File file) {
175         return repository.getId() + '.' + file.getName() + LAST_UPDATE_TAG;
176     }
177 
178     String getRepositoryKey(ArtifactRepository repository) {
179         StringBuilder buffer = new StringBuilder(256);
180 
181         Proxy proxy = repository.getProxy();
182         if (proxy != null) {
183             if (proxy.getUserName() != null) {
184                 int hash = (proxy.getUserName() + proxy.getPassword()).hashCode();
185                 buffer.append(hash).append('@');
186             }
187             buffer.append(proxy.getHost()).append(':').append(proxy.getPort()).append('>');
188         }
189 
190         // consider the username&password because a repo manager might block artifacts depending on authorization
191         Authentication auth = repository.getAuthentication();
192         if (auth != null) {
193             int hash = (auth.getUsername() + auth.getPassword()).hashCode();
194             buffer.append(hash).append('@');
195         }
196 
197         // consider the URL (instead of the id) as this most closely relates to the contents in the repo
198         buffer.append(repository.getUrl());
199 
200         return buffer.toString();
201     }
202 
203     private void writeLastUpdated(File touchfile, String key, String error) {
204         synchronized (touchfile.getAbsolutePath().intern()) {
205             if (!touchfile.getParentFile().exists()
206                     && !touchfile.getParentFile().mkdirs()) {
207                 getLogger()
208                         .debug("Failed to create directory: " + touchfile.getParent()
209                                 + " for tracking artifact metadata resolution.");
210                 return;
211             }
212 
213             FileChannel channel = null;
214             FileLock lock = null;
215             try {
216                 Properties props = new Properties();
217 
218                 channel = new RandomAccessFile(touchfile, "rw").getChannel();
219                 lock = channel.lock();
220 
221                 if (touchfile.canRead()) {
222                     getLogger().debug("Reading resolution-state from: " + touchfile);
223                     props.load(Channels.newInputStream(channel));
224                 }
225 
226                 props.setProperty(key, Long.toString(System.currentTimeMillis()));
227 
228                 if (error != null) {
229                     props.setProperty(key + ERROR_KEY_SUFFIX, error);
230                 } else {
231                     props.remove(key + ERROR_KEY_SUFFIX);
232                 }
233 
234                 getLogger().debug("Writing resolution-state to: " + touchfile);
235                 channel.truncate(0);
236                 props.store(Channels.newOutputStream(channel), "Last modified on: " + new Date());
237 
238                 lock.release();
239                 lock = null;
240 
241                 channel.close();
242                 channel = null;
243             } catch (IOException e) {
244                 getLogger()
245                         .debug(
246                                 "Failed to record lastUpdated information for resolution.\nFile: "
247                                         + touchfile.toString() + "; key: " + key,
248                                 e);
249             } finally {
250                 if (lock != null) {
251                     try {
252                         lock.release();
253                     } catch (IOException e) {
254                         getLogger()
255                                 .debug("Error releasing exclusive lock for resolution tracking file: " + touchfile, e);
256                     }
257                 }
258 
259                 if (channel != null) {
260                     try {
261                         channel.close();
262                     } catch (IOException e) {
263                         getLogger().debug("Error closing FileChannel for resolution tracking file: " + touchfile, e);
264                     }
265                 }
266             }
267         }
268     }
269 
270     Date readLastUpdated(File touchfile, String key) {
271         getLogger().debug("Searching for " + key + " in resolution tracking file.");
272 
273         Properties props = read(touchfile);
274         if (props != null) {
275             String rawVal = props.getProperty(key);
276             if (rawVal != null) {
277                 try {
278                     return new Date(Long.parseLong(rawVal));
279                 } catch (NumberFormatException e) {
280                     getLogger().debug("Cannot parse lastUpdated date: '" + rawVal + "'. Ignoring.", e);
281                 }
282             }
283         }
284         return null;
285     }
286 
287     private String getError(File touchFile, String key) {
288         Properties props = read(touchFile);
289         if (props != null) {
290             return props.getProperty(key + ERROR_KEY_SUFFIX);
291         }
292         return null;
293     }
294 
295     private Properties read(File touchfile) {
296         if (!touchfile.canRead()) {
297             getLogger().debug("Skipped unreadable resolution tracking file: " + touchfile);
298             return null;
299         }
300 
301         synchronized (touchfile.getAbsolutePath().intern()) {
302             try {
303                 Properties props = new Properties();
304 
305                 try (FileInputStream in = new FileInputStream(touchfile)) {
306                     try (FileLock lock = in.getChannel().lock(0, Long.MAX_VALUE, true)) {
307                         getLogger().debug("Reading resolution-state from: " + touchfile);
308                         props.load(in);
309 
310                         return props;
311                     }
312                 }
313 
314             } catch (IOException e) {
315                 getLogger().debug("Failed to read resolution tracking file: " + touchfile, e);
316 
317                 return null;
318             }
319         }
320     }
321 
322     File getTouchfile(Artifact artifact) {
323         StringBuilder sb = new StringBuilder(128);
324         sb.append(artifact.getArtifactId());
325         sb.append('-').append(artifact.getBaseVersion());
326         if (artifact.getClassifier() != null) {
327             sb.append('-').append(artifact.getClassifier());
328         }
329         sb.append('.').append(artifact.getType()).append(LAST_UPDATE_TAG);
330         return new File(artifact.getFile().getParentFile(), sb.toString());
331     }
332 
333     File getTouchfile(RepositoryMetadata metadata, File file) {
334         return new File(file.getParent(), TOUCHFILE_NAME);
335     }
336 }