001 package org.apache.maven.artifact.repository.metadata;
002
003 /*
004 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
005 * agreements. See the NOTICE file distributed with this work for additional information regarding
006 * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance with the License. You may obtain a
008 * copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed under the License
013 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
014 * or implied. See the License for the specific language governing permissions and limitations under
015 * the License.
016 */
017
018 import java.io.File;
019 import java.io.FileNotFoundException;
020 import java.io.IOException;
021 import java.io.Reader;
022 import java.io.Writer;
023 import java.util.Date;
024 import java.util.HashMap;
025 import java.util.List;
026 import java.util.Map;
027
028 import org.apache.maven.artifact.metadata.ArtifactMetadata;
029 import org.apache.maven.artifact.repository.ArtifactRepository;
030 import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
031 import org.apache.maven.artifact.repository.DefaultRepositoryRequest;
032 import org.apache.maven.artifact.repository.RepositoryRequest;
033 import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader;
034 import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Writer;
035 import org.apache.maven.repository.legacy.UpdateCheckManager;
036 import org.apache.maven.repository.legacy.WagonManager;
037 import org.apache.maven.wagon.ResourceDoesNotExistException;
038 import org.apache.maven.wagon.TransferFailedException;
039 import org.codehaus.plexus.component.annotations.Component;
040 import org.codehaus.plexus.component.annotations.Requirement;
041 import org.codehaus.plexus.logging.AbstractLogEnabled;
042 import org.codehaus.plexus.util.IOUtil;
043 import org.codehaus.plexus.util.ReaderFactory;
044 import org.codehaus.plexus.util.WriterFactory;
045 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
046
047 /**
048 * @author Jason van Zyl
049 */
050 @Component(role=RepositoryMetadataManager.class)
051 public class DefaultRepositoryMetadataManager
052 extends AbstractLogEnabled
053 implements RepositoryMetadataManager
054 {
055 @Requirement
056 private WagonManager wagonManager;
057
058 @Requirement
059 private UpdateCheckManager updateCheckManager;
060
061 public void resolve( RepositoryMetadata metadata, List<ArtifactRepository> remoteRepositories, ArtifactRepository localRepository )
062 throws RepositoryMetadataResolutionException
063 {
064 RepositoryRequest request = new DefaultRepositoryRequest();
065 request.setLocalRepository( localRepository );
066 request.setRemoteRepositories( remoteRepositories );
067 resolve( metadata, request );
068 }
069
070 public void resolve( RepositoryMetadata metadata, RepositoryRequest request )
071 throws RepositoryMetadataResolutionException
072 {
073 ArtifactRepository localRepository = request.getLocalRepository();
074 List<ArtifactRepository> remoteRepositories = request.getRemoteRepositories();
075
076 if ( !request.isOffline() )
077 {
078 Date localCopyLastModified = null;
079 if ( metadata.getBaseVersion() != null )
080 {
081 localCopyLastModified = getLocalCopyLastModified( localRepository, metadata );
082 }
083
084 for ( ArtifactRepository repository : remoteRepositories )
085 {
086 ArtifactRepositoryPolicy policy = metadata.getPolicy( repository );
087
088 File file =
089 new File( localRepository.getBasedir(), localRepository.pathOfLocalRepositoryMetadata( metadata,
090 repository ) );
091 boolean update;
092
093 if ( !policy.isEnabled() )
094 {
095 update = false;
096
097 if ( getLogger().isDebugEnabled() )
098 {
099 getLogger().debug(
100 "Skipping update check for " + metadata.getKey() + " (" + file
101 + ") from disabled repository " + repository.getId() + " ("
102 + repository.getUrl() + ")" );
103 }
104 }
105 else if ( request.isForceUpdate() )
106 {
107 update = true;
108 }
109 else if ( localCopyLastModified != null && !policy.checkOutOfDate( localCopyLastModified ) )
110 {
111 update = false;
112
113 if ( getLogger().isDebugEnabled() )
114 {
115 getLogger().debug(
116 "Skipping update check for " + metadata.getKey() + " (" + file
117 + ") from repository " + repository.getId() + " (" + repository.getUrl()
118 + ") in favor of local copy" );
119 }
120 }
121 else if ( updateCheckManager.isUpdateRequired( metadata, repository, file ) )
122 {
123 update = true;
124 }
125 else
126 {
127 update = false;
128 }
129
130 if ( update )
131 {
132 getLogger().info( metadata.getKey() + ": checking for updates from " + repository.getId() );
133 try
134 {
135 wagonManager.getArtifactMetadata( metadata, repository, file, policy.getChecksumPolicy() );
136 }
137 catch ( ResourceDoesNotExistException e )
138 {
139 getLogger().debug( metadata + " could not be found on repository: " + repository.getId() );
140
141 // delete the local copy so the old details aren't used.
142 if ( file.exists() )
143 {
144 file.delete();
145 }
146 }
147 catch ( TransferFailedException e )
148 {
149 getLogger().warn( metadata + " could not be retrieved from repository: " + repository.getId()
150 + " due to an error: " + e.getMessage() );
151 getLogger().debug( "Exception", e );
152 }
153 finally
154 {
155 updateCheckManager.touch( metadata, repository, file );
156 }
157 }
158
159 // TODO: should this be inside the above check?
160 // touch file so that this is not checked again until interval has passed
161 if ( file.exists() )
162 {
163 file.setLastModified( System.currentTimeMillis() );
164 }
165 }
166 }
167
168 try
169 {
170 mergeMetadata( metadata, remoteRepositories, localRepository );
171 }
172 catch ( RepositoryMetadataStoreException e )
173 {
174 throw new RepositoryMetadataResolutionException( "Unable to store local copy of metadata: " + e.getMessage(), e );
175 }
176 }
177
178 private Date getLocalCopyLastModified( ArtifactRepository localRepository, RepositoryMetadata metadata )
179 {
180 String metadataPath = localRepository.pathOfLocalRepositoryMetadata( metadata, localRepository );
181 File metadataFile = new File( localRepository.getBasedir(), metadataPath );
182 return metadataFile.isFile() ? new Date( metadataFile.lastModified() ) : null;
183 }
184
185 private void mergeMetadata( RepositoryMetadata metadata, List<ArtifactRepository> remoteRepositories, ArtifactRepository localRepository )
186 throws RepositoryMetadataStoreException
187 {
188 // TODO: currently this is first wins, but really we should take the latest by comparing either the
189 // snapshot timestamp, or some other timestamp later encoded into the metadata.
190 // TODO: this needs to be repeated here so the merging doesn't interfere with the written metadata
191 // - we'd be much better having a pristine input, and an ongoing metadata for merging instead
192
193 Map<ArtifactRepository, Metadata> previousMetadata = new HashMap<ArtifactRepository, Metadata>();
194 ArtifactRepository selected = null;
195 for ( ArtifactRepository repository : remoteRepositories )
196 {
197 ArtifactRepositoryPolicy policy = metadata.getPolicy( repository );
198
199 if ( policy.isEnabled() && 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<ArtifactRepository, Metadata> previousMetadata, ArtifactRepository selected, ArtifactRepository localRepository )
215 throws RepositoryMetadataStoreException
216 {
217 // TODO: this could be a lot nicer... should really be in the snapshot transformation?
218 if ( metadata.isSnapshot() )
219 {
220 Metadata prevMetadata = metadata.getMetadata();
221
222 for ( ArtifactRepository repository : previousMetadata.keySet() )
223 {
224 Metadata m = previousMetadata.get( repository );
225 if ( repository.equals( selected ) )
226 {
227 if ( m.getVersioning() == null )
228 {
229 m.setVersioning( new Versioning() );
230 }
231
232 if ( m.getVersioning().getSnapshot() == null )
233 {
234 m.getVersioning().setSnapshot( new Snapshot() );
235 }
236 }
237 else
238 {
239 if ( ( m.getVersioning() != null ) && ( m.getVersioning().getSnapshot() != null ) && m.getVersioning().getSnapshot().isLocalCopy() )
240 {
241 m.getVersioning().getSnapshot().setLocalCopy( false );
242 metadata.setMetadata( m );
243 metadata.storeInLocalRepository( localRepository, repository );
244 }
245 }
246 }
247
248 metadata.setMetadata( prevMetadata );
249 }
250 }
251
252 private boolean loadMetadata( RepositoryMetadata repoMetadata, ArtifactRepository remoteRepository, ArtifactRepository localRepository, Map<ArtifactRepository, Metadata> previousMetadata )
253 {
254 boolean setRepository = false;
255
256 File metadataFile = new File( localRepository.getBasedir(), localRepository.pathOfLocalRepositoryMetadata( repoMetadata, remoteRepository ) );
257
258 if ( metadataFile.exists() )
259 {
260 Metadata metadata;
261
262 try
263 {
264 metadata = readMetadata( metadataFile );
265 }
266 catch ( RepositoryMetadataReadException e )
267 {
268 if ( getLogger().isDebugEnabled() )
269 {
270 getLogger().warn( e.getMessage(), e );
271 }
272 else
273 {
274 getLogger().warn( e.getMessage() );
275 }
276 return setRepository;
277 }
278
279 if ( repoMetadata.isSnapshot() && ( previousMetadata != null ) )
280 {
281 previousMetadata.put( remoteRepository, metadata );
282 }
283
284 if ( repoMetadata.getMetadata() != null )
285 {
286 setRepository = repoMetadata.getMetadata().merge( metadata );
287 }
288 else
289 {
290 repoMetadata.setMetadata( metadata );
291 setRepository = true;
292 }
293 }
294 return setRepository;
295 }
296
297 /** @todo share with DefaultPluginMappingManager. */
298 protected Metadata readMetadata( File mappingFile )
299 throws RepositoryMetadataReadException
300 {
301 Metadata result;
302
303 Reader reader = null;
304 try
305 {
306 reader = ReaderFactory.newXmlReader( mappingFile );
307
308 MetadataXpp3Reader mappingReader = new MetadataXpp3Reader();
309
310 result = mappingReader.read( reader, false );
311 }
312 catch ( FileNotFoundException e )
313 {
314 throw new RepositoryMetadataReadException( "Cannot read metadata from '" + mappingFile + "'", e );
315 }
316 catch ( IOException e )
317 {
318 throw new RepositoryMetadataReadException( "Cannot read metadata from '" + mappingFile + "': " + e.getMessage(), e );
319 }
320 catch ( XmlPullParserException e )
321 {
322 throw new RepositoryMetadataReadException( "Cannot read metadata from '" + mappingFile + "': " + e.getMessage(), e );
323 }
324 finally
325 {
326 IOUtil.close( reader );
327 }
328
329 return result;
330 }
331
332 /**
333 * Ensures the last updated timestamp of the specified metadata does not refer to the future and fixes the local metadata if necessary to allow
334 * proper merging/updating of metadata during deployment.
335 */
336 private void fixTimestamp( File metadataFile, Metadata metadata, Metadata reference )
337 {
338 boolean changed = false;
339
340 if ( metadata != null && reference != null )
341 {
342 Versioning versioning = metadata.getVersioning();
343 Versioning versioningRef = reference.getVersioning();
344 if ( versioning != null && versioningRef != null )
345 {
346 String lastUpdated = versioning.getLastUpdated();
347 String now = versioningRef.getLastUpdated();
348 if ( lastUpdated != null && now != null && now.compareTo( lastUpdated ) < 0 )
349 {
350 getLogger().warn(
351 "The last updated timestamp in " + metadataFile + " refers to the future (now = "
352 + now + ", lastUpdated = " + lastUpdated
353 + "). Please verify that the clocks of all"
354 + " deploying machines are reasonably synchronized." );
355 versioning.setLastUpdated( now );
356 changed = true;
357 }
358 }
359 }
360
361 if ( changed )
362 {
363 getLogger().debug( "Repairing metadata in " + metadataFile );
364
365 Writer writer = null;
366 try
367 {
368 writer = WriterFactory.newXmlWriter( metadataFile );
369 new MetadataXpp3Writer().write( writer, metadata );
370 }
371 catch ( IOException e )
372 {
373 String msg = "Could not write fixed metadata to " + metadataFile + ": " + e.getMessage();
374 if ( getLogger().isDebugEnabled() )
375 {
376 getLogger().warn( msg, e );
377 }
378 else
379 {
380 getLogger().warn( msg );
381 }
382 }
383 finally
384 {
385 IOUtil.close( writer );
386 }
387 }
388 }
389
390 public void resolveAlways( RepositoryMetadata metadata, ArtifactRepository localRepository, ArtifactRepository remoteRepository )
391 throws RepositoryMetadataResolutionException
392 {
393 File file;
394 try
395 {
396 file = getArtifactMetadataFromDeploymentRepository( metadata, localRepository, remoteRepository );
397 }
398 catch ( TransferFailedException e )
399 {
400 throw new RepositoryMetadataResolutionException( metadata + " could not be retrieved from repository: " + remoteRepository.getId() + " due to an error: " + e.getMessage(), e );
401 }
402
403 try
404 {
405 if ( file.exists() )
406 {
407 Metadata prevMetadata = readMetadata( file );
408 metadata.setMetadata( prevMetadata );
409 }
410 }
411 catch ( RepositoryMetadataReadException e )
412 {
413 throw new RepositoryMetadataResolutionException( e.getMessage(), e );
414 }
415 }
416
417 private File getArtifactMetadataFromDeploymentRepository( ArtifactMetadata metadata, ArtifactRepository localRepository, ArtifactRepository remoteRepository )
418 throws TransferFailedException
419 {
420 File file = new File( localRepository.getBasedir(), localRepository.pathOfLocalRepositoryMetadata( metadata, remoteRepository ) );
421
422 try
423 {
424 wagonManager.getArtifactMetadataFromDeploymentRepository( metadata, remoteRepository, file, ArtifactRepositoryPolicy.CHECKSUM_POLICY_WARN );
425 }
426 catch ( ResourceDoesNotExistException e )
427 {
428 getLogger().info( metadata + " could not be found on repository: " + remoteRepository.getId() + ", so will be created" );
429
430 // delete the local copy so the old details aren't used.
431 if ( file.exists() )
432 {
433 file.delete();
434 }
435 }
436 finally
437 {
438 if ( metadata instanceof RepositoryMetadata )
439 {
440 updateCheckManager.touch( (RepositoryMetadata) metadata, remoteRepository, file );
441 }
442 }
443 return file;
444 }
445
446 public void deploy( ArtifactMetadata metadata, ArtifactRepository localRepository, ArtifactRepository deploymentRepository )
447 throws RepositoryMetadataDeploymentException
448 {
449 File file;
450 if ( metadata instanceof RepositoryMetadata )
451 {
452 getLogger().info( "Retrieving previous metadata from " + deploymentRepository.getId() );
453 try
454 {
455 file = getArtifactMetadataFromDeploymentRepository( metadata, localRepository, deploymentRepository );
456 }
457 catch ( TransferFailedException e )
458 {
459 throw new RepositoryMetadataDeploymentException( metadata + " could not be retrieved from repository: " + deploymentRepository.getId() + " due to an error: " + e.getMessage(), e );
460 }
461
462 if ( file.isFile() )
463 {
464 try
465 {
466 fixTimestamp( file, readMetadata( file ), ( (RepositoryMetadata) metadata ).getMetadata() );
467 }
468 catch ( RepositoryMetadataReadException e )
469 {
470 // will be reported via storeInlocalRepository
471 }
472 }
473 }
474 else
475 {
476 // It's a POM - we don't need to retrieve it first
477 file = new File( localRepository.getBasedir(), localRepository.pathOfLocalRepositoryMetadata( metadata, deploymentRepository ) );
478 }
479
480 try
481 {
482 metadata.storeInLocalRepository( localRepository, deploymentRepository );
483 }
484 catch ( RepositoryMetadataStoreException e )
485 {
486 throw new RepositoryMetadataDeploymentException( "Error installing metadata: " + e.getMessage(), e );
487 }
488
489 try
490 {
491 wagonManager.putArtifactMetadata( file, metadata, deploymentRepository );
492 }
493 catch ( TransferFailedException e )
494 {
495 throw new RepositoryMetadataDeploymentException( "Error while deploying metadata: " + e.getMessage(), e );
496 }
497 }
498
499 public void install( ArtifactMetadata metadata, ArtifactRepository localRepository )
500 throws RepositoryMetadataInstallationException
501 {
502 try
503 {
504 metadata.storeInLocalRepository( localRepository, localRepository );
505 }
506 catch ( RepositoryMetadataStoreException e )
507 {
508 throw new RepositoryMetadataInstallationException( "Error installing metadata: " + e.getMessage(), e );
509 }
510 }
511
512 }