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