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    }