001    package org.apache.archiva.stagerepository.merge;
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 org.apache.archiva.common.utils.VersionComparator;
023    import org.apache.archiva.common.utils.VersionUtil;
024    import org.apache.archiva.configuration.ArchivaConfiguration;
025    import org.apache.archiva.configuration.Configuration;
026    import org.apache.archiva.configuration.ManagedRepositoryConfiguration;
027    import org.apache.archiva.maven2.metadata.MavenMetadataReader;
028    import org.apache.archiva.metadata.model.ArtifactMetadata;
029    import org.apache.archiva.metadata.repository.MetadataRepository;
030    import org.apache.archiva.metadata.repository.MetadataRepositoryException;
031    import org.apache.archiva.metadata.repository.filter.Filter;
032    import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator;
033    import org.apache.archiva.model.ArchivaRepositoryMetadata;
034    import org.apache.archiva.repository.RepositoryException;
035    import org.apache.archiva.repository.metadata.RepositoryMetadataException;
036    import org.apache.archiva.repository.metadata.RepositoryMetadataWriter;
037    import org.apache.archiva.xml.XMLException;
038    import org.apache.commons.io.FileUtils;
039    import org.slf4j.Logger;
040    import org.slf4j.LoggerFactory;
041    import org.springframework.stereotype.Service;
042    
043    import javax.inject.Inject;
044    import javax.inject.Named;
045    import java.io.File;
046    import java.io.IOException;
047    import java.text.DateFormat;
048    import java.text.SimpleDateFormat;
049    import java.util.ArrayList;
050    import java.util.Calendar;
051    import java.util.Collections;
052    import java.util.Date;
053    import java.util.List;
054    import java.util.TimeZone;
055    import java.util.regex.Pattern;
056    
057    /**
058     *
059     */
060    @Service ("repositoryMerger#maven2")
061    public class Maven2RepositoryMerger
062        implements RepositoryMerger
063    {
064    
065        private Logger log = LoggerFactory.getLogger( getClass() );
066    
067        /**
068         *
069         */
070        private ArchivaConfiguration configuration;
071    
072        /**
073         *
074         */
075        private RepositoryPathTranslator pathTranslator;
076    
077        private static final String METADATA_FILENAME = "maven-metadata.xml";
078    
079        @Inject
080        public Maven2RepositoryMerger(
081            @Named (value = "archivaConfiguration#default") ArchivaConfiguration archivaConfiguration,
082            @Named (value = "repositoryPathTranslator#maven2") RepositoryPathTranslator repositoryPathTranslator )
083        {
084            this.configuration = archivaConfiguration;
085            this.pathTranslator = repositoryPathTranslator;
086        }
087    
088        public void setConfiguration( ArchivaConfiguration configuration )
089        {
090            this.configuration = configuration;
091        }
092    
093        public void merge( MetadataRepository metadataRepository, String sourceRepoId, String targetRepoId )
094            throws RepositoryMergerException
095        {
096    
097            try
098            {
099                List<ArtifactMetadata> artifactsInSourceRepo = metadataRepository.getArtifacts( sourceRepoId );
100                for ( ArtifactMetadata artifactMetadata : artifactsInSourceRepo )
101                {
102                    artifactMetadata.setRepositoryId( targetRepoId );
103                    createFolderStructure( sourceRepoId, targetRepoId, artifactMetadata );
104                }
105            }
106            catch ( MetadataRepositoryException e )
107            {
108                throw new RepositoryMergerException( e.getMessage(), e );
109            }
110            catch ( IOException e )
111            {
112                throw new RepositoryMergerException( e.getMessage(), e );
113            }
114            catch ( RepositoryException e )
115            {
116                throw new RepositoryMergerException( e.getMessage(), e );
117            }
118        }
119    
120        // TODO when UI needs a subset to merge
121        public void merge( MetadataRepository metadataRepository, String sourceRepoId, String targetRepoId,
122                           Filter<ArtifactMetadata> filter )
123            throws RepositoryMergerException
124        {
125            try
126            {
127                List<ArtifactMetadata> sourceArtifacts = metadataRepository.getArtifacts( sourceRepoId );
128                for ( ArtifactMetadata metadata : sourceArtifacts )
129                {
130                    if ( filter.accept( metadata ) )
131                    {
132                        createFolderStructure( sourceRepoId, targetRepoId, metadata );
133                    }
134                }
135            }
136            catch ( MetadataRepositoryException e )
137            {
138                throw new RepositoryMergerException( e.getMessage(), e );
139            }
140            catch ( IOException e )
141            {
142                throw new RepositoryMergerException( e.getMessage(), e );
143            }
144            catch ( RepositoryException e )
145            {
146                throw new RepositoryMergerException( e.getMessage(), e );
147            }
148        }
149    
150        private void createFolderStructure( String sourceRepoId, String targetRepoId, ArtifactMetadata artifactMetadata )
151            throws IOException, RepositoryException
152        {
153            Configuration config = configuration.getConfiguration();
154    
155            ManagedRepositoryConfiguration targetRepoConfig = config.findManagedRepositoryById( targetRepoId );
156    
157            ManagedRepositoryConfiguration sourceRepoConfig = config.findManagedRepositoryById( sourceRepoId );
158    
159            Date lastUpdatedTimestamp = Calendar.getInstance().getTime();
160    
161            TimeZone timezone = TimeZone.getTimeZone( "UTC" );
162    
163            DateFormat fmt = new SimpleDateFormat( "yyyyMMdd.HHmmss" );
164    
165            fmt.setTimeZone( timezone );
166    
167            String timestamp = fmt.format( lastUpdatedTimestamp );
168    
169            String targetRepoPath = targetRepoConfig.getLocation();
170    
171            String sourceRepoPath = sourceRepoConfig.getLocation();
172    
173            String artifactPath = pathTranslator.toPath( artifactMetadata.getNamespace(), artifactMetadata.getProject(),
174                                                         artifactMetadata.getProjectVersion(), artifactMetadata.getId() );
175    
176            File sourceArtifactFile = new File( sourceRepoPath, artifactPath );
177    
178            File targetArtifactFile = new File( targetRepoPath, artifactPath );
179    
180            log.debug( "artifactPath {}", artifactPath );
181    
182            int lastIndex = artifactPath.lastIndexOf( RepositoryPathTranslator.PATH_SEPARATOR );
183    
184            File targetFile = new File( targetRepoPath, artifactPath.substring( 0, lastIndex ) );
185    
186            if ( !targetFile.exists() )
187            {
188                // create the folder structure when it does not exist
189                targetFile.mkdirs();
190            }
191            // artifact copying
192            copyFile( sourceArtifactFile, targetArtifactFile );
193    
194            // pom file copying
195            String fileName = artifactMetadata.getProject() + "-" + artifactMetadata.getVersion() + ".pom";
196    
197            // pom file copying
198            // TODO need to use path translator to get the pom file path
199    //        String fileName = artifactMetadata.getProject() + "-" + artifactMetadata.getVersion() + ".pom";
200    //
201    //        File sourcePomFile =
202    //            pathTranslator.toFile( new File( sourceRepoPath ), artifactMetadata.getId(), artifactMetadata.getProject(),
203    //                                   artifactMetadata.getVersion(), fileName );
204    //
205    //        String relativePathToPomFile = sourcePomFile.getAbsolutePath().split( sourceRepoPath )[1];
206    //        File targetPomFile = new File( targetRepoPath, relativePathToPomFile );
207    
208            //pom file copying  (file path is taken with out using path translator)
209    
210            String index = artifactPath.substring( lastIndex + 1 );
211            int last = index.lastIndexOf( '.' );
212            File sourcePomFile = new File( sourceRepoPath,
213                                           artifactPath.substring( 0, lastIndex ) + "/" + artifactPath.substring(
214                                               lastIndex + 1 ).substring( 0, last ) + ".pom" );
215            File targetPomFile = new File( targetRepoPath,
216                                           artifactPath.substring( 0, lastIndex ) + "/" + artifactPath.substring(
217                                               lastIndex + 1 ).substring( 0, last ) + ".pom" );
218    
219            if ( !targetPomFile.exists() && sourcePomFile.exists() )
220            {
221                copyFile( sourcePomFile, targetPomFile );
222            }
223    
224            // explicitly update only if metadata-updater consumer is not enabled!
225            if ( !config.getRepositoryScanning().getKnownContentConsumers().contains( "metadata-updater" ) )
226            {
227    
228                // updating version metadata files
229                File versionMetaDataFileInSourceRepo =
230                    pathTranslator.toFile( new File( sourceRepoPath ), artifactMetadata.getNamespace(),
231                                           artifactMetadata.getProject(), artifactMetadata.getVersion(),
232                                           METADATA_FILENAME );
233    
234                if ( versionMetaDataFileInSourceRepo.exists() )
235                {//Pattern quote for windows path
236                    String relativePathToVersionMetadataFile =
237                        versionMetaDataFileInSourceRepo.getAbsolutePath().split( Pattern.quote( sourceRepoPath ) )[1];
238                    File versionMetaDataFileInTargetRepo = new File( targetRepoPath, relativePathToVersionMetadataFile );
239    
240                    if ( !versionMetaDataFileInTargetRepo.exists() )
241                    {
242                        copyFile( versionMetaDataFileInSourceRepo, versionMetaDataFileInTargetRepo );
243                    }
244                    else
245                    {
246                        updateVersionMetadata( versionMetaDataFileInTargetRepo, artifactMetadata, lastUpdatedTimestamp );
247    
248                    }
249                }
250    
251                // updating project meta data file
252                String projectDirectoryInSourceRepo = new File( versionMetaDataFileInSourceRepo.getParent() ).getParent();
253                File projectMetadataFileInSourceRepo = new File( projectDirectoryInSourceRepo, METADATA_FILENAME );
254    
255                if ( projectMetadataFileInSourceRepo.exists() )
256                {
257                    String relativePathToProjectMetadataFile =
258                        projectMetadataFileInSourceRepo.getAbsolutePath().split( Pattern.quote( sourceRepoPath ) )[1];
259                    File projectMetadataFileInTargetRepo = new File( targetRepoPath, relativePathToProjectMetadataFile );
260    
261                    if ( !projectMetadataFileInTargetRepo.exists() )
262                    {
263    
264                        copyFile( projectMetadataFileInSourceRepo, projectMetadataFileInTargetRepo );
265                    }
266                    else
267                    {
268                        updateProjectMetadata( projectMetadataFileInTargetRepo, artifactMetadata, lastUpdatedTimestamp,
269                                               timestamp );
270                    }
271                }
272            }
273    
274        }
275    
276        private void copyFile( File sourceFile, File targetFile )
277            throws IOException
278        {
279    
280            FileUtils.copyFile( sourceFile, targetFile );
281    
282        }
283    
284        private void updateProjectMetadata( File projectMetaDataFileIntargetRepo, ArtifactMetadata artifactMetadata,
285                                            Date lastUpdatedTimestamp, String timestamp )
286            throws RepositoryMetadataException
287        {
288            ArrayList<String> availableVersions = new ArrayList<String>();
289            String latestVersion = artifactMetadata.getProjectVersion();
290    
291            ArchivaRepositoryMetadata projectMetadata = getMetadata( projectMetaDataFileIntargetRepo );
292    
293            if ( projectMetaDataFileIntargetRepo.exists() )
294            {
295                availableVersions = (ArrayList<String>) projectMetadata.getAvailableVersions();
296    
297                Collections.sort( availableVersions, VersionComparator.getInstance() );
298    
299                if ( !availableVersions.contains( artifactMetadata.getVersion() ) )
300                {
301                    availableVersions.add( artifactMetadata.getVersion() );
302                }
303    
304                latestVersion = availableVersions.get( availableVersions.size() - 1 );
305            }
306            else
307            {
308                availableVersions.add( artifactMetadata.getProjectVersion() );
309                projectMetadata.setGroupId( artifactMetadata.getNamespace() );
310                projectMetadata.setArtifactId( artifactMetadata.getProject() );
311            }
312    
313            if ( projectMetadata.getGroupId() == null )
314            {
315                projectMetadata.setGroupId( artifactMetadata.getNamespace() );
316            }
317    
318            if ( projectMetadata.getArtifactId() == null )
319            {
320                projectMetadata.setArtifactId( artifactMetadata.getProject() );
321            }
322    
323            projectMetadata.setLatestVersion( latestVersion );
324            projectMetadata.setAvailableVersions( availableVersions );
325            projectMetadata.setLastUpdated( timestamp );
326            projectMetadata.setLastUpdatedTimestamp( lastUpdatedTimestamp );
327    
328            if ( !VersionUtil.isSnapshot( artifactMetadata.getVersion() ) )
329            {
330                projectMetadata.setReleasedVersion( latestVersion );
331            }
332    
333            RepositoryMetadataWriter.write( projectMetadata, projectMetaDataFileIntargetRepo );
334    
335        }
336    
337        private void updateVersionMetadata( File versionMetaDataFileInTargetRepo, ArtifactMetadata artifactMetadata,
338                                            Date lastUpdatedTimestamp )
339            throws RepositoryMetadataException
340        {
341            ArchivaRepositoryMetadata versionMetadata = getMetadata( versionMetaDataFileInTargetRepo );
342            if ( !versionMetaDataFileInTargetRepo.exists() )
343            {
344                versionMetadata.setGroupId( artifactMetadata.getNamespace() );
345                versionMetadata.setArtifactId( artifactMetadata.getProject() );
346                versionMetadata.setVersion( artifactMetadata.getProjectVersion() );
347            }
348    
349            versionMetadata.setLastUpdatedTimestamp( lastUpdatedTimestamp );
350            RepositoryMetadataWriter.write( versionMetadata, versionMetaDataFileInTargetRepo );
351        }
352    
353        private ArchivaRepositoryMetadata getMetadata( File metadataFile )
354            throws RepositoryMetadataException
355        {
356            ArchivaRepositoryMetadata metadata = new ArchivaRepositoryMetadata();
357            if ( metadataFile.exists() )
358            {
359                try
360                {
361                    metadata = MavenMetadataReader.read( metadataFile );
362                }
363                catch ( XMLException e )
364                {
365                    throw new RepositoryMetadataException( e.getMessage(), e );
366                }
367            }
368            return metadata;
369        }
370    
371        public List<ArtifactMetadata> getConflictingArtifacts( MetadataRepository metadataRepository, String sourceRepo,
372                                                               String targetRepo )
373            throws RepositoryMergerException
374        {
375            try
376            {
377                List<ArtifactMetadata> targetArtifacts = metadataRepository.getArtifacts( targetRepo );
378                List<ArtifactMetadata> sourceArtifacts = metadataRepository.getArtifacts( sourceRepo );
379                List<ArtifactMetadata> conflictsArtifacts = new ArrayList<ArtifactMetadata>();
380    
381                for ( ArtifactMetadata targetArtifact : targetArtifacts )
382                {
383                    for ( ArtifactMetadata sourceArtifact : sourceArtifacts )
384                    {
385                        if ( isEquals( targetArtifact, sourceArtifact ) )
386                        {
387                            if ( !conflictsArtifacts.contains( sourceArtifact ) )
388                            {
389                                conflictsArtifacts.add( sourceArtifact );
390                            }
391                        }
392                    }
393                }
394    
395                sourceArtifacts.removeAll( conflictsArtifacts );
396    
397                return conflictsArtifacts;
398            }
399            catch ( MetadataRepositoryException e )
400            {
401                throw new RepositoryMergerException( e.getMessage(), e );
402            }
403        }
404    
405        private boolean isEquals( ArtifactMetadata sourceArtifact, ArtifactMetadata targetArtifact )
406        {
407            boolean isSame = false;
408    
409            if ( ( sourceArtifact.getNamespace().equals( targetArtifact.getNamespace() ) )
410                && ( sourceArtifact.getProject().equals( targetArtifact.getProject() ) ) && ( sourceArtifact.getId().equals(
411                targetArtifact.getId() ) ) && ( sourceArtifact.getProjectVersion().equals(
412                targetArtifact.getProjectVersion() ) ) )
413    
414            {
415                isSame = true;
416    
417            }
418    
419            return isSame;
420        }
421    }