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    }