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