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