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