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 }