View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a 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,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.wrapper;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.net.URI;
24  import java.nio.file.DirectoryStream;
25  import java.nio.file.FileVisitResult;
26  import java.nio.file.Files;
27  import java.nio.file.Path;
28  import java.nio.file.Paths;
29  import java.nio.file.SimpleFileVisitor;
30  import java.nio.file.StandardCopyOption;
31  import java.nio.file.attribute.BasicFileAttributes;
32  import java.nio.file.attribute.PosixFilePermission;
33  import java.nio.file.attribute.PosixFilePermissions;
34  import java.util.ArrayList;
35  import java.util.Enumeration;
36  import java.util.List;
37  import java.util.Locale;
38  import java.util.Set;
39  import java.util.zip.ZipEntry;
40  import java.util.zip.ZipException;
41  import java.util.zip.ZipFile;
42  
43  /**
44   * Maven distribution installer, eventually using a {@link Downloader} first.
45   *
46   * @author Hans Dockter
47   */
48  public class Installer {
49      public static final Path DEFAULT_DISTRIBUTION_PATH = Paths.get("wrapper", "dists");
50  
51      private final Downloader download;
52  
53      private final Verifier verifier;
54  
55      private final PathAssembler pathAssembler;
56  
57      public Installer(Downloader download, Verifier verifier, PathAssembler pathAssembler) {
58          this.download = download;
59          this.verifier = verifier;
60          this.pathAssembler = pathAssembler;
61      }
62  
63      public Path createDist(WrapperConfiguration configuration) throws Exception {
64          URI distributionUrl = configuration.getDistribution();
65  
66          boolean alwaysDownload = configuration.isAlwaysDownload();
67          boolean alwaysUnpack = configuration.isAlwaysUnpack();
68          boolean verifyDistributionSha256Sum =
69                  !configuration.getDistributionSha256Sum().isEmpty();
70  
71          PathAssembler.LocalDistribution localDistribution = pathAssembler.getDistribution(configuration);
72          Path localZipFile = localDistribution.getZipFile();
73  
74          if (alwaysDownload || alwaysUnpack || Files.notExists(localZipFile)) {
75              Logger.info("Installing Maven distribution "
76                      + localDistribution.getDistributionDir().toAbsolutePath());
77          }
78  
79          boolean downloaded = false;
80          if (alwaysDownload || Files.notExists(localZipFile)) {
81              Logger.info("Downloading " + distributionUrl);
82              Path tmpZipFile = localZipFile.resolveSibling(localZipFile.getFileName() + ".part");
83              Files.deleteIfExists(tmpZipFile);
84              download.download(distributionUrl, tmpZipFile);
85              Files.move(tmpZipFile, localZipFile, StandardCopyOption.REPLACE_EXISTING);
86              downloaded = Files.exists(localZipFile);
87          }
88  
89          Path distDir = localDistribution.getDistributionDir();
90          List<Path> dirs = listDirs(distDir);
91  
92          if (downloaded || alwaysUnpack || dirs.isEmpty()) {
93              if (verifyDistributionSha256Sum) {
94                  verifier.verify(
95                          localZipFile,
96                          "distributionSha256Sum",
97                          Verifier.SHA_256_ALGORITHM,
98                          configuration.getDistributionSha256Sum());
99              }
100             for (Path dir : dirs) {
101                 Logger.info("Deleting directory " + dir.toAbsolutePath());
102                 deleteDir(dir);
103             }
104             Logger.info("Unzipping " + localZipFile.toAbsolutePath() + " to " + distDir.toAbsolutePath());
105             unzip(localZipFile, distDir);
106             dirs = listDirs(distDir);
107             if (dirs.isEmpty()) {
108                 throw new RuntimeException(String.format(
109                         Locale.ROOT,
110                         "Maven distribution '%s' does not contain any directory."
111                                 + " Expected to find exactly 1 directory.",
112                         distDir));
113             }
114             setExecutablePermissions(dirs.get(0));
115         }
116         if (dirs.size() != 1) {
117             throw new RuntimeException(String.format(
118                     Locale.ROOT,
119                     "Maven distribution '%s' contains too many directories." + " Expected to find exactly 1 directory.",
120                     distDir));
121         }
122         return dirs.get(0);
123     }
124 
125     private List<Path> listDirs(Path distDir) throws IOException {
126         List<Path> dirs = new ArrayList<>();
127         if (Files.exists(distDir)) {
128             try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(distDir)) {
129                 for (Path file : dirStream) {
130                     if (Files.isDirectory(file)) {
131                         dirs.add(file);
132                     }
133                 }
134             }
135         }
136         return dirs;
137     }
138 
139     private void setExecutablePermissions(Path mavenHome) {
140         if (isWindows()) {
141             return;
142         }
143         Path mavenCommand = mavenHome.resolve("bin/mvn");
144         try {
145             Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rwxr-xr-x");
146             Files.setPosixFilePermissions(mavenCommand, perms);
147         } catch (IOException e) {
148             Logger.warn("Could not set executable permissions for: " + mavenCommand.toAbsolutePath()
149                     + ". Please do this manually if you want to use Maven.");
150         }
151     }
152 
153     private boolean isWindows() {
154         String osName = System.getProperty("os.name").toLowerCase(Locale.US);
155         return osName.contains("windows");
156     }
157 
158     private void deleteDir(Path dirPath) throws IOException {
159         Files.walkFileTree(dirPath, new SimpleFileVisitor<Path>() {
160             @Override
161             public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
162                 Files.delete(dir);
163                 if (exc != null) {
164                     throw exc;
165                 }
166                 return FileVisitResult.CONTINUE;
167             }
168 
169             @Override
170             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
171                 Files.delete(file);
172                 return FileVisitResult.CONTINUE;
173             }
174         });
175     }
176 
177     public void unzip(Path zip, Path dest) throws IOException {
178         final Path destDir = dest.normalize();
179         try (ZipFile zipFile = new ZipFile(zip.toFile())) {
180             final Enumeration<? extends ZipEntry> entries = zipFile.entries();
181 
182             while (entries.hasMoreElements()) {
183                 final ZipEntry entry = entries.nextElement();
184 
185                 Path fileEntry = destDir.resolve(entry.getName()).normalize();
186                 if (!fileEntry.startsWith(destDir)) {
187                     throw new ZipException("Zip includes an invalid entry: " + entry.getName());
188                 }
189 
190                 if (entry.isDirectory()) {
191                     continue;
192                 }
193 
194                 Files.createDirectories(fileEntry.getParent());
195 
196                 try (InputStream inStream = zipFile.getInputStream(entry)) {
197                     Files.copy(inStream, fileEntry);
198                 }
199             }
200         }
201     }
202 }