1 package org.apache.maven.artifact.repository.metadata;
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.manager.WagonManager;
23 import org.apache.maven.artifact.metadata.ArtifactMetadata;
24 import org.apache.maven.artifact.repository.ArtifactRepository;
25 import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
26 import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader;
27 import org.apache.maven.wagon.ResourceDoesNotExistException;
28 import org.apache.maven.wagon.TransferFailedException;
29 import org.codehaus.plexus.logging.AbstractLogEnabled;
30 import org.codehaus.plexus.util.IOUtil;
31 import org.codehaus.plexus.util.ReaderFactory;
32 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
33
34 import java.io.File;
35 import java.io.FileNotFoundException;
36 import java.io.IOException;
37 import java.io.Reader;
38 import java.util.Date;
39 import java.util.HashMap;
40 import java.util.HashSet;
41 import java.util.Iterator;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Set;
45
46 public class DefaultRepositoryMetadataManager
47 extends AbstractLogEnabled
48 implements RepositoryMetadataManager
49 {
50 // component requirement
51 private WagonManager wagonManager;
52
53 /**
54 * @todo very primitive. Probably we can cache artifacts themselves in a central location, as well as reset the flag over time in a long running process.
55 */
56 private Set cachedMetadata = new HashSet();
57
58 public void resolve( RepositoryMetadata metadata, List remoteRepositories, ArtifactRepository localRepository )
59 throws RepositoryMetadataResolutionException
60 {
61 boolean alreadyResolved = alreadyResolved( metadata );
62 if ( !alreadyResolved )
63 {
64 for ( Iterator i = remoteRepositories.iterator(); i.hasNext(); )
65 {
66 ArtifactRepository repository = (ArtifactRepository) i.next();
67
68 ArtifactRepositoryPolicy policy =
69 metadata.isSnapshot() ? repository.getSnapshots() : repository.getReleases();
70
71 if ( !policy.isEnabled() )
72 {
73 getLogger().debug( "Skipping disabled repository " + repository.getId() );
74 }
75 else if ( repository.isBlacklisted() )
76 {
77 getLogger().debug( "Skipping blacklisted repository " + repository.getId() );
78 }
79 else
80 {
81 File file = new File( localRepository.getBasedir(),
82 localRepository.pathOfLocalRepositoryMetadata( metadata, repository ) );
83
84
85 boolean checkForUpdates =
86 !file.exists() || policy.checkOutOfDate( new Date( file.lastModified() ) );
87
88 if ( checkForUpdates )
89 {
90 if ( wagonManager.isOnline() )
91 {
92 getLogger().info( metadata.getKey() + ": checking for updates from " + repository.getId() );
93
94 boolean storeMetadata = false;
95 try
96 {
97 wagonManager.getArtifactMetadata( metadata, repository, file,
98 policy.getChecksumPolicy() );
99 storeMetadata = true;
100 }
101 catch ( ResourceDoesNotExistException e )
102 {
103 getLogger().debug(
104 metadata + " could not be found on repository: " + repository.getId() );
105
106 // delete the local copy so the old details aren't used.
107 if ( file.exists() )
108 {
109 file.delete();
110 }
111 storeMetadata = true;
112 }
113 catch ( TransferFailedException e )
114 {
115 getLogger().warn( metadata + " could not be retrieved from repository: " +
116 repository.getId() + " due to an error: " + e.getMessage() );
117 getLogger().debug( "Exception", e );
118
119 getLogger().info( "Repository '" + repository.getId() + "' will be blacklisted" );
120 repository.setBlacklisted( true );
121
122 // TODO: [jc; 08-Nov-2005] revisit this for 2.1
123 // suppressing logging to avoid logging this error twice.
124 }
125 if ( storeMetadata )
126 {
127 // touch file so that this is not checked again until interval has passed
128 if ( file.exists() )
129 {
130 file.setLastModified( System.currentTimeMillis() );
131 }
132 else
133 {
134 // this ensures that files are not continuously checked when they don't exist remotely
135
136 // TODO: [jdcasey] If this happens as a result of ResourceDoesNotExistException, what effect will it have on subsequent runs?
137 // Will the updateInterval come into play cleanly, or will this plug up the works??
138 try
139 {
140 metadata.storeInLocalRepository( localRepository, repository );
141 }
142 catch ( RepositoryMetadataStoreException e )
143 {
144 throw new RepositoryMetadataResolutionException(
145 "Unable to store local copy of metadata: " + e.getMessage(), e );
146 }
147 }
148 }
149 }
150 else
151 {
152 getLogger().debug( "System is offline. Cannot resolve metadata:\n" +
153 metadata.extendedToString() + "\n\n" );
154 }
155 }
156 }
157 }
158
159 // TODO: [jdcasey] what happens here when the system is offline, or there is a TransferFailedException
160 // ...and no metadata file is written?
161 cachedMetadata.add( metadata.getKey() );
162 }
163
164 try
165 {
166 mergeMetadata( metadata, remoteRepositories, localRepository );
167 }
168 catch ( RepositoryMetadataStoreException e )
169 {
170 throw new RepositoryMetadataResolutionException(
171 "Unable to store local copy of metadata: " + e.getMessage(), e );
172 }
173 catch ( RepositoryMetadataReadException e )
174 {
175 throw new RepositoryMetadataResolutionException( "Unable to read local copy of metadata: " + e.getMessage(),
176 e );
177 }
178 }
179
180 private void mergeMetadata( RepositoryMetadata metadata, List remoteRepositories,
181 ArtifactRepository localRepository )
182 throws RepositoryMetadataStoreException, RepositoryMetadataReadException
183 {
184 // TODO: currently this is first wins, but really we should take the latest by comparing either the
185 // snapshot timestamp, or some other timestamp later encoded into the metadata.
186 // TODO: this needs to be repeated here so the merging doesn't interfere with the written metadata
187 // - we'd be much better having a pristine input, and an ongoing metadata for merging instead
188
189 Map previousMetadata = new HashMap();
190 ArtifactRepository selected = null;
191 for ( Iterator i = remoteRepositories.iterator(); i.hasNext(); )
192 {
193 ArtifactRepository repository = (ArtifactRepository) i.next();
194
195 ArtifactRepositoryPolicy policy =
196 metadata.isSnapshot() ? repository.getSnapshots() : repository.getReleases();
197
198 if ( ( policy.isEnabled() && !repository.isBlacklisted() )
199 && ( loadMetadata( metadata, repository, localRepository, previousMetadata ) ) )
200 {
201 metadata.setRepository( repository );
202 selected = repository;
203 }
204 }
205 if ( loadMetadata( metadata, localRepository, localRepository, previousMetadata ) )
206 {
207 metadata.setRepository( null );
208 selected = localRepository;
209 }
210
211 updateSnapshotMetadata( metadata, previousMetadata, selected, localRepository );
212 }
213
214 private void updateSnapshotMetadata( RepositoryMetadata metadata, Map previousMetadata, ArtifactRepository selected,
215 ArtifactRepository localRepository )
216 throws RepositoryMetadataStoreException
217 {
218 // TODO: this could be a lot nicer... should really be in the snapshot transformation?
219 if ( metadata.isSnapshot() )
220 {
221 Metadata prevMetadata = metadata.getMetadata();
222
223 for ( Iterator i = previousMetadata.keySet().iterator(); i.hasNext(); )
224 {
225 ArtifactRepository repository = (ArtifactRepository) i.next();
226 Metadata m = (Metadata) previousMetadata.get( repository );
227 if ( repository.equals( selected ) )
228 {
229 if ( m.getVersioning() == null )
230 {
231 m.setVersioning( new Versioning() );
232 }
233
234 if ( m.getVersioning().getSnapshot() == null )
235 {
236 m.getVersioning().setSnapshot( new Snapshot() );
237 }
238 /*
239 if ( !m.getVersioning().getSnapshot().isLocalCopy() )
240 {
241 // TODO: I think this is incorrect (it results in localCopy set in a remote profile). Probably
242 // harmless so not removing at this point until full tests in place.
243 m.getVersioning().getSnapshot().setLocalCopy( true );
244 metadata.setMetadata( m );
245 metadata.storeInLocalRepository( localRepository, repository );
246 }
247 */
248 }
249 else
250 {
251 if ( ( m.getVersioning() != null ) && ( m.getVersioning().getSnapshot() != null ) &&
252 m.getVersioning().getSnapshot().isLocalCopy() )
253 {
254 m.getVersioning().getSnapshot().setLocalCopy( false );
255 metadata.setMetadata( m );
256 metadata.storeInLocalRepository( localRepository, repository );
257 }
258 }
259 }
260
261 metadata.setMetadata( prevMetadata );
262 }
263 }
264
265 private boolean loadMetadata( RepositoryMetadata repoMetadata, ArtifactRepository remoteRepository,
266 ArtifactRepository localRepository, Map previousMetadata )
267 throws RepositoryMetadataReadException
268 {
269 boolean setRepository = false;
270
271 File metadataFile = new File( localRepository.getBasedir(),
272 localRepository.pathOfLocalRepositoryMetadata( repoMetadata, remoteRepository ) );
273
274 if ( metadataFile.exists() )
275 {
276 Metadata metadata = readMetadata( metadataFile );
277
278 if ( repoMetadata.isSnapshot() && ( previousMetadata != null ) )
279 {
280 previousMetadata.put( remoteRepository, metadata );
281 }
282
283 if ( repoMetadata.getMetadata() != null )
284 {
285 setRepository = repoMetadata.getMetadata().merge( metadata );
286 }
287 else
288 {
289 repoMetadata.setMetadata( metadata );
290 setRepository = true;
291 }
292 }
293 return setRepository;
294 }
295
296 /**
297 * @todo share with DefaultPluginMappingManager.
298 */
299 protected static Metadata readMetadata( File mappingFile )
300 throws RepositoryMetadataReadException
301 {
302 Metadata result;
303
304 Reader reader = null;
305 try
306 {
307 reader = ReaderFactory.newXmlReader( mappingFile );
308
309 MetadataXpp3Reader mappingReader = new MetadataXpp3Reader();
310
311 result = mappingReader.read( reader, false );
312 }
313 catch ( FileNotFoundException e )
314 {
315 throw new RepositoryMetadataReadException( "Cannot read metadata from '" + mappingFile + "'", e );
316 }
317 catch ( IOException e )
318 {
319 throw new RepositoryMetadataReadException(
320 "Cannot read metadata from '" + mappingFile + "': " + e.getMessage(), e );
321 }
322 catch ( XmlPullParserException e )
323 {
324 throw new RepositoryMetadataReadException(
325 "Cannot read metadata from '" + mappingFile + "': " + e.getMessage(), e );
326 }
327 finally
328 {
329 IOUtil.close( reader );
330 }
331 return result;
332 }
333
334 public void resolveAlways( RepositoryMetadata metadata, ArtifactRepository localRepository,
335 ArtifactRepository remoteRepository )
336 throws RepositoryMetadataResolutionException
337 {
338 if ( !wagonManager.isOnline() )
339 {
340 // metadata is required for deployment, can't be offline
341 throw new RepositoryMetadataResolutionException(
342 "System is offline. Cannot resolve required metadata:\n" + metadata.extendedToString() );
343 }
344
345 File file;
346 try
347 {
348 file = getArtifactMetadataFromDeploymentRepository( metadata, localRepository, remoteRepository );
349 }
350 catch ( TransferFailedException e )
351 {
352 throw new RepositoryMetadataResolutionException( metadata + " could not be retrieved from repository: " +
353 remoteRepository.getId() + " due to an error: " + e.getMessage(), e );
354 }
355
356 try
357 {
358 if ( file.exists() )
359 {
360 Metadata prevMetadata = readMetadata( file );
361 metadata.setMetadata( prevMetadata );
362 }
363 }
364 catch ( RepositoryMetadataReadException e )
365 {
366 throw new RepositoryMetadataResolutionException( e.getMessage(), e );
367 }
368 }
369
370 private File getArtifactMetadataFromDeploymentRepository( ArtifactMetadata metadata,
371 ArtifactRepository localRepository,
372 ArtifactRepository remoteRepository )
373 throws TransferFailedException
374 {
375 File file = new File( localRepository.getBasedir(),
376 localRepository.pathOfLocalRepositoryMetadata( metadata, remoteRepository ) );
377
378 try
379 {
380 wagonManager.getArtifactMetadataFromDeploymentRepository( metadata, remoteRepository, file,
381 ArtifactRepositoryPolicy.CHECKSUM_POLICY_WARN );
382 }
383 catch ( ResourceDoesNotExistException e )
384 {
385 getLogger().info(
386 metadata + " could not be found on repository: " + remoteRepository.getId() + ", so will be created" );
387
388 // delete the local copy so the old details aren't used.
389 if ( file.exists() )
390 {
391 file.delete();
392 }
393 }
394 return file;
395 }
396
397 private boolean alreadyResolved( ArtifactMetadata metadata )
398 {
399 return cachedMetadata.contains( metadata.getKey() );
400 }
401
402 public void deploy( ArtifactMetadata metadata, ArtifactRepository localRepository,
403 ArtifactRepository deploymentRepository )
404 throws RepositoryMetadataDeploymentException
405 {
406 if ( !wagonManager.isOnline() )
407 {
408 // deployment shouldn't silently fail when offline
409 throw new RepositoryMetadataDeploymentException(
410 "System is offline. Cannot deploy metadata:\n" + metadata.extendedToString() );
411 }
412
413 File file;
414 if ( metadata instanceof RepositoryMetadata )
415 {
416 getLogger().info( "Retrieving previous metadata from " + deploymentRepository.getId() );
417 try
418 {
419 file = getArtifactMetadataFromDeploymentRepository( metadata, localRepository, deploymentRepository );
420 }
421 catch ( TransferFailedException e )
422 {
423 throw new RepositoryMetadataDeploymentException( metadata +
424 " could not be retrieved from repository: " + deploymentRepository.getId() + " due to an error: " +
425 e.getMessage(), e );
426 }
427 }
428 else
429 {
430 // It's a POM - we don't need to retrieve it first
431 file = new File( localRepository.getBasedir(),
432 localRepository.pathOfLocalRepositoryMetadata( metadata, deploymentRepository ) );
433 }
434
435 try
436 {
437 metadata.storeInLocalRepository( localRepository, deploymentRepository );
438 }
439 catch ( RepositoryMetadataStoreException e )
440 {
441 throw new RepositoryMetadataDeploymentException( "Error installing metadata: " + e.getMessage(), e );
442 }
443
444 try
445 {
446 wagonManager.putArtifactMetadata( file, metadata, deploymentRepository );
447 }
448 catch ( TransferFailedException e )
449 {
450 throw new RepositoryMetadataDeploymentException( "Error while deploying metadata: " + e.getMessage(), e );
451 }
452 }
453
454 public void install( ArtifactMetadata metadata, ArtifactRepository localRepository )
455 throws RepositoryMetadataInstallationException
456 {
457 try
458 {
459 metadata.storeInLocalRepository( localRepository, localRepository );
460 }
461 catch ( RepositoryMetadataStoreException e )
462 {
463 throw new RepositoryMetadataInstallationException( "Error installing metadata: " + e.getMessage(), e );
464 }
465 }
466 }