View Javadoc
1   package org.codehaus.mojo.wagon.shared;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
5    * agreements. See the NOTICE file distributed with this work for additional information regarding
6    * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance with the License. You may obtain a
8    * copy of the License at
9    * 
10   * http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   * Unless required by applicable law or agreed to in writing, software distributed under the License
13   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
14   * or implied. See the License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  
18  import java.io.File;
19  import java.io.FileInputStream;
20  import java.io.FileReader;
21  import java.io.FileWriter;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.Reader;
25  import java.io.Writer;
26  import java.security.MessageDigest;
27  import java.security.NoSuchAlgorithmException;
28  
29  import org.apache.maven.artifact.repository.metadata.Metadata;
30  import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader;
31  import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Writer;
32  import org.apache.maven.plugin.logging.Log;
33  import org.apache.maven.shared.model.fileset.FileSet;
34  import org.apache.maven.wagon.ResourceDoesNotExistException;
35  import org.apache.maven.wagon.Wagon;
36  import org.apache.maven.wagon.WagonException;
37  import org.codehaus.plexus.util.DirectoryScanner;
38  import org.codehaus.plexus.util.FileUtils;
39  import org.codehaus.plexus.util.IOUtil;
40  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
41  
42  /**
43   * A copy of stage's plugin RepositoryCopier but use WagonUpload and WagonDownload instead
44   * 
45   * @plexus.component role="org.codehaus.mojo.wagon.shared.MavenRepoMerger" role-hint="default"
46   */
47  
48  public class DefaultMavenRepoMerger
49      implements MavenRepoMerger
50  {
51      /**
52       * @plexus.requirement role="org.codehaus.mojo.wagon.shared.WagonDownload"
53       */
54      private WagonDownload downloader;
55  
56      /**
57       * @plexus.requirement role="org.codehaus.mojo.wagon.shared.WagonUpload"
58       */
59      private WagonUpload uploader;
60  
61      public void merge( Wagon src, Wagon target, boolean optimize, Log logger )
62          throws WagonException, IOException
63      {
64  
65          // copy src to a local dir
66          File downloadSrcDir =  createTempDirectory( "wagon-maven-plugin" );
67  
68          WagonFileSet srcFileSet = new WagonFileSet();
69          srcFileSet.setDownloadDirectory( downloadSrcDir );
70          // ignore archiva/nexus .index at root dir
71          String[] excludes = { ".index/**", ".indexer/**, .meta/**, .nexus/**" };
72          srcFileSet.setExcludes( excludes );
73  
74          try
75          {
76              downloader.download( src, srcFileSet, logger );
77  
78              // merge metadata
79              DirectoryScanner scanner = new DirectoryScanner();
80              scanner.setBasedir( downloadSrcDir );
81              String[] includes = { "**/" + MAVEN_METADATA };
82              scanner.setIncludes( includes );
83              scanner.scan();
84              String[] files = scanner.getIncludedFiles();
85  
86              for ( int i = 0; i < files.length; ++i )
87              {
88                  File srcMetadaFile = new File( downloadSrcDir, files[i] + IN_PROCESS_MARKER );
89  
90                  try
91                  {
92                      target.get( files[i].replace( '\\', '/' ), srcMetadaFile );
93                  }
94                  catch ( ResourceDoesNotExistException e )
95                  {
96                      // We don't have an equivalent on the targetRepositoryUrl side because we have something
97                      // new on the sourceRepositoryUrl side so just skip the metadata merging.
98                      continue;
99                  }
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 }