001    package org.codehaus.mojo.wagon.shared;
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.FileInputStream;
020    import java.io.FileReader;
021    import java.io.FileWriter;
022    import java.io.IOException;
023    import java.io.InputStream;
024    import java.io.Reader;
025    import java.io.Writer;
026    import java.security.MessageDigest;
027    import java.security.NoSuchAlgorithmException;
028    
029    import org.apache.maven.artifact.repository.metadata.Metadata;
030    import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader;
031    import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Writer;
032    import org.apache.maven.plugin.logging.Log;
033    import org.apache.maven.shared.model.fileset.FileSet;
034    import org.apache.maven.wagon.ResourceDoesNotExistException;
035    import org.apache.maven.wagon.Wagon;
036    import org.apache.maven.wagon.WagonException;
037    import org.codehaus.plexus.util.DirectoryScanner;
038    import org.codehaus.plexus.util.FileUtils;
039    import org.codehaus.plexus.util.IOUtil;
040    import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
041    
042    /**
043     * A copy of stage's plugin RepositoryCopier but use WagonUpload and WagonDownload instead
044     * 
045     * @plexus.component role="org.codehaus.mojo.wagon.shared.MavenRepoMerger" role-hint="default"
046     */
047    
048    public class DefaultMavenRepoMerger
049        implements MavenRepoMerger
050    {
051        /**
052         * @plexus.requirement role="org.codehaus.mojo.wagon.shared.WagonDownload"
053         */
054        private WagonDownload downloader;
055    
056        /**
057         * @plexus.requirement role="org.codehaus.mojo.wagon.shared.WagonUpload"
058         */
059        private WagonUpload uploader;
060    
061        public void merge( Wagon src, Wagon target, boolean optimize, Log logger )
062            throws WagonException, IOException
063        {
064    
065            // copy src to a local dir
066            File downloadSrcDir =  createTempDirectory( "wagon-maven-plugin" );
067    
068            WagonFileSet srcFileSet = new WagonFileSet();
069            srcFileSet.setDownloadDirectory( downloadSrcDir );
070            // ignore archiva/nexus .index at root dir
071            String[] excludes = { ".index/**", ".indexer/**, .meta/**, .nexus/**" };
072            srcFileSet.setExcludes( excludes );
073    
074            try
075            {
076                downloader.download( src, srcFileSet, logger );
077    
078                // merge metadata
079                DirectoryScanner scanner = new DirectoryScanner();
080                scanner.setBasedir( downloadSrcDir );
081                String[] includes = { "**/" + MAVEN_METADATA };
082                scanner.setIncludes( includes );
083                scanner.scan();
084                String[] files = scanner.getIncludedFiles();
085    
086                for ( int i = 0; i < files.length; ++i )
087                {
088                    File srcMetadaFile = new File( downloadSrcDir, files[i] + IN_PROCESS_MARKER );
089    
090                    try
091                    {
092                        target.get( files[i].replace( '\\', '/' ), srcMetadaFile );
093                    }
094                    catch ( ResourceDoesNotExistException e )
095                    {
096                        // We don't have an equivalent on the targetRepositoryUrl side because we have something
097                        // new on the sourceRepositoryUrl side so just skip the metadata merging.
098                        continue;
099                    }
100    
101                    try
102                    {
103                        mergeMetadata( srcMetadaFile, logger );
104                    }
105                    catch ( XmlPullParserException e )
106                    {
107                        throw new IOException( "Metadata file is corrupt " + files[i] + " Reason: " + e.getMessage() );
108                    }
109    
110                }
111    
112                // upload to target
113                FileSet tobeUploadedFileSet = new FileSet();
114                tobeUploadedFileSet.setDirectory( downloadSrcDir.getAbsolutePath() );
115    
116                this.uploader.upload( target, tobeUploadedFileSet, optimize, logger );
117    
118            }
119            finally
120            {
121                FileUtils.deleteDirectory( downloadSrcDir );
122            }
123    
124        }
125    
126        private void mergeMetadata( File existingMetadata, Log logger )
127            throws IOException, XmlPullParserException
128        {
129    
130            Writer stagedMetadataWriter = null;
131            Reader existingMetadataReader = null;
132            Reader stagedMetadataReader = null;
133            File stagedMetadataFile = null;
134    
135            try
136            {
137                MetadataXpp3Reader xppReader = new MetadataXpp3Reader();
138                MetadataXpp3Writer xppWriter = new MetadataXpp3Writer();
139    
140                // Existing Metadata in target stage
141                existingMetadataReader = new FileReader( existingMetadata );
142                Metadata existing = xppReader.read( existingMetadataReader );
143    
144                // Staged Metadata
145                stagedMetadataFile = new File( existingMetadata.getParentFile(), MAVEN_METADATA );
146                stagedMetadataReader = new FileReader( stagedMetadataFile );
147                Metadata staged = xppReader.read( stagedMetadataReader );
148    
149                // Merge and write back to staged metadata to replace the remote one
150                existing.merge( staged );
151    
152                stagedMetadataWriter = new FileWriter( stagedMetadataFile );
153                xppWriter.write( stagedMetadataWriter, existing );
154    
155                logger.info( "Merging metadata file: " + stagedMetadataFile );
156    
157            }
158            finally
159            {
160                IOUtil.close( stagedMetadataWriter );
161                IOUtil.close( stagedMetadataReader );
162                IOUtil.close( existingMetadataReader );
163    
164                existingMetadata.delete();
165            }
166    
167            // Mark all metadata as in-process and regenerate the checksums as they will be different
168            // after the merger
169    
170            try
171            {
172                File newMd5 = new File( stagedMetadataFile.getParentFile(), MAVEN_METADATA + ".md5" );
173                FileUtils.fileWrite( newMd5.getAbsolutePath(), checksum( stagedMetadataFile, MD5 ) );
174    
175                File newSha1 = new File( stagedMetadataFile.getParentFile(), MAVEN_METADATA + ".sha1" );
176                FileUtils.fileWrite( newSha1.getAbsolutePath(), checksum( stagedMetadataFile, SHA1 ) );
177            }
178            catch ( NoSuchAlgorithmException e )
179            {
180                throw new RuntimeException( e );
181            }
182    
183            // We have the new merged copy so we're good
184    
185        }
186    
187        private String checksum( File file, String type )
188            throws IOException, NoSuchAlgorithmException
189        {
190            MessageDigest md5 = MessageDigest.getInstance( type );
191    
192            InputStream is = new FileInputStream( file );
193    
194            byte[] buf = new byte[8192];
195    
196            int i;
197    
198            while ( ( i = is.read( buf ) ) > 0 )
199            {
200                md5.update( buf, 0, i );
201            }
202    
203            IOUtil.close( is );
204    
205            return encode( md5.digest() );
206        }
207    
208        private String encode( byte[] binaryData )
209        {
210            if ( binaryData.length != 16 && binaryData.length != 20 )
211            {
212                int bitLength = binaryData.length * 8;
213                throw new IllegalArgumentException( "Unrecognised length for binary data: " + bitLength + " bits" );
214            }
215    
216            String retValue = "";
217    
218            for ( int i = 0; i < binaryData.length; i++ )
219            {
220                String t = Integer.toHexString( binaryData[i] & 0xff );
221    
222                if ( t.length() == 1 )
223                {
224                    retValue += ( "0" + t );
225                }
226                else
227                {
228                    retValue += t;
229                }
230            }
231    
232            return retValue.trim();
233        }
234    
235        public static File createTempDirectory(String prefix)
236            throws IOException
237        {
238            final File temp;
239    
240            temp = File.createTempFile( prefix, Long.toString( System.nanoTime() ) );
241    
242            if ( !( temp.delete() ) )
243            {
244                throw new IOException( "Could not delete temp file: " + temp.getAbsolutePath() );
245            }
246    
247            if ( !( temp.mkdir() ) )
248            {
249                throw new IOException( "Could not create temp directory: " + temp.getAbsolutePath() );
250            }
251    
252            return ( temp );
253        }
254    
255    }