1 /*
2  * Copyright 2008-2019 by Emeric Vernat
3  *
4  *     This file is part of Java Melody.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * 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, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */

18 package net.bull.javamelody.internal.model; // NOPMD
19
20 import java.io.BufferedInputStream;
21 import java.io.ByteArrayInputStream;
22 import java.io.File;
23 import java.io.FileInputStream;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.OutputStream;
28 import java.io.Serializable;
29 import java.net.URL;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.Collections;
33 import java.util.HashMap;
34 import java.util.LinkedHashMap;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Properties;
38 import java.util.Set;
39 import java.util.TreeMap;
40 import java.util.zip.ZipEntry;
41 import java.util.zip.ZipInputStream;
42
43 import javax.servlet.ServletContext;
44 import javax.xml.parsers.DocumentBuilder;
45 import javax.xml.parsers.DocumentBuilderFactory;
46 import javax.xml.parsers.ParserConfigurationException;
47
48 import org.w3c.dom.Document;
49 import org.w3c.dom.Node;
50 import org.w3c.dom.NodeList;
51 import org.xml.sax.SAXException;
52
53 import net.bull.javamelody.Parameter;
54 import net.bull.javamelody.internal.common.InputOutput;
55 import net.bull.javamelody.internal.common.LOG;
56 import net.bull.javamelody.internal.common.Parameters;
57
58 /**
59  * Lecture d'artifacts Maven.
60  * @author Emeric Vernat
61  */

62 public final class MavenArtifact implements Serializable {
63     private static final long serialVersionUID = 1L;
64
65     private static final String MAVEN_CENTRAL = "https://repo1.maven.org/maven2";
66
67     private static final File LOCAL_REPO = new File(
68             System.getProperty("user.home") + "/.m2/repository");
69
70     private static final String TOMCAT_ARCHIVES = "https://archive.apache.org/dist/tomcat/";
71
72     private static Map<String, String> sourceFilePathsByJarFileNames;
73
74     private static String webappVersion;
75
76     private String name;
77     private String url;
78     private String groupId;
79     private String artifactId;
80     private String version;
81     private MavenArtifact parent;
82     private final Map<String, String> licenseUrlsByName = new LinkedHashMap<>();
83     private Map<String, String> properties;
84     private final List<MavenArtifact> dependencies = new ArrayList<>();
85     private final List<MavenArtifact> managedDependencies = new ArrayList<>();
86     private boolean updated;
87
88     private MavenArtifact() {
89         super();
90     }
91
92     private static MavenArtifact parseDependency(URL jarFileLocation) throws IOException {
93         final byte[] pomXml = readMavenFileFromJarFile(jarFileLocation, "pom.xml");
94         if (pomXml != null) {
95             final MavenArtifact dependency = new MavenArtifact();
96             dependency.parsePomXml(new ByteArrayInputStream(pomXml));
97             return dependency;
98         }
99         return null;
100     }
101
102     private void parsePomXml(InputStream pomXml) throws IOException {
103         final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
104         try {
105             final DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
106             final Document doc = dBuilder.parse(pomXml);
107             final Node projectNode = doc.getElementsByTagName("project").item(0);
108             final NodeList childNodes = projectNode.getChildNodes();
109             properties = new HashMap<>();
110             for (int i = 0; i < childNodes.getLength(); i++) {
111                 final Node node = childNodes.item(i);
112                 parseNode(node);
113             }
114             properties.put("project.groupId", groupId);
115             properties.put("pom.groupId", groupId);
116             properties.put("groupId", groupId);
117             properties.put("project.version", version);
118             properties.put("pom.version", version);
119             properties.put("version", version);
120             for (final MavenArtifact dependency : dependencies) {
121                 dependency.groupId = replaceProperty(dependency.groupId, properties);
122                 dependency.version = replaceProperty(dependency.version, properties);
123             }
124             for (final MavenArtifact dependency : managedDependencies) {
125                 dependency.groupId = replaceProperty(dependency.groupId, properties);
126                 dependency.version = replaceProperty(dependency.version, properties);
127             }
128             properties = null;
129         } catch (final ParserConfigurationException | SAXException e) {
130             throw new IOException(e.getMessage(), e);
131         }
132         updated = true;
133     }
134
135     // CHECKSTYLE:OFF
136     private void parseNode(Node node) {
137         // CHECKSTYLE:ON
138         final String nodeName = node.getNodeName();
139         if ("name".equals(nodeName)) {
140             this.name = node.getTextContent();
141         } else if ("description".equals(nodeName) && this.name == null) {
142             this.name = node.getTextContent();
143         } else if ("url".equals(nodeName)) {
144             this.url = node.getTextContent();
145         } else if ("groupId".equals(nodeName) && !node.getTextContent().startsWith("${")) {
146             this.groupId = node.getTextContent();
147         } else if ("artifactId".equals(nodeName)) {
148             this.artifactId = node.getTextContent();
149         } else if ("version".equals(nodeName) && !node.getTextContent().startsWith("${")) {
150             this.version = node.getTextContent();
151         } else if ("parent".equals(nodeName)) {
152             parseParentNode(node);
153         } else if ("properties".equals(nodeName)) {
154             properties.putAll(parsePropertiesNode(node));
155         } else if ("licenses".equals(nodeName)) {
156             parseLicensesNode(node);
157         } else if ("dependencies".equals(nodeName)) {
158             this.dependencies.addAll(parseDependenciesNode(node));
159         } else if ("dependencyManagement".equals(nodeName)) {
160             final NodeList childNodes = node.getChildNodes();
161             for (int i = 0; i < childNodes.getLength(); i++) {
162                 final Node childNode = childNodes.item(i);
163                 if ("dependencies".equals(childNode.getNodeName())) {
164                     this.managedDependencies.addAll(parseDependenciesNode(childNode));
165                 }
166             }
167         }
168     }
169
170     private void parseParentNode(Node node) {
171         final NodeList parentNodes = node.getChildNodes();
172         String parentGroupId = null;
173         String parentArtifactId = null;
174         String parentVersion = null;
175         for (int k = 0; k < parentNodes.getLength(); k++) {
176             final Node childParentNode = parentNodes.item(k);
177             final String nodeName = childParentNode.getNodeName();
178             if ("groupId".equals(nodeName)) {
179                 parentGroupId = childParentNode.getTextContent();
180             } else if ("artifactId".equals(nodeName)) {
181                 parentArtifactId = childParentNode.getTextContent();
182             } else if ("version".equals(nodeName)) {
183                 parentVersion = childParentNode.getTextContent();
184             }
185         }
186         if (this.groupId == null) {
187             this.groupId = parentGroupId;
188         }
189         if (this.version == null) {
190             this.version = parentVersion;
191         }
192         this.parent = new MavenArtifact();
193         this.parent.groupId = parentGroupId;
194         this.parent.artifactId = parentArtifactId;
195         this.parent.version = parentVersion;
196     }
197
198     private Map<String, String> parsePropertiesNode(Node propertiesNode) {
199         final Map<String, String> props = new HashMap<>();
200         final NodeList propertiesNodes = propertiesNode.getChildNodes();
201         for (int j = 0; j < propertiesNodes.getLength(); j++) {
202             final Node propertyNode = propertiesNodes.item(j);
203             final String nodeName = propertyNode.getNodeName();
204             if (nodeName != null) {
205                 props.put(nodeName, propertyNode.getTextContent());
206             }
207         }
208         return props;
209     }
210
211     private void parseLicensesNode(Node licensesNode) {
212         final NodeList licenseNodes = licensesNode.getChildNodes();
213         for (int j = 0; j < licenseNodes.getLength(); j++) {
214             final Node licenseNode = licenseNodes.item(j);
215             if ("license".equals(licenseNode.getNodeName())) {
216                 String licenseName = null;
217                 String licenseUrl = null;
218                 final NodeList childLicenseNodes = licenseNode.getChildNodes();
219                 for (int k = 0; k < childLicenseNodes.getLength(); k++) {
220                     final Node childLicenseNode = childLicenseNodes.item(k);
221                     final String nodeName = childLicenseNode.getNodeName();
222                     if ("name".equals(nodeName)) {
223                         licenseName = childLicenseNode.getTextContent();
224                     } else if ("url".equals(nodeName)) {
225                         licenseUrl = childLicenseNode.getTextContent();
226                     }
227                 }
228                 if (licenseName != null) {
229                     licenseUrlsByName.put(licenseName, licenseUrl);
230                 } else if (licenseUrl != null) {
231                     if (licenseUrl.startsWith("http")) {
232                         licenseUrlsByName.put("LICENSE", licenseUrl);
233                     } else {
234                         licenseUrlsByName.put(licenseUrl, licenseUrl);
235                     }
236                 }
237             }
238         }
239     }
240
241     // CHECKSTYLE:OFF
242     private List<MavenArtifact> parseDependenciesNode(Node dependenciesNode) {
243         // CHECKSTYLE:ON
244         final List<MavenArtifact> deps = new ArrayList<>();
245         final NodeList dependencyNodes = dependenciesNode.getChildNodes();
246         for (int j = 0; j < dependencyNodes.getLength(); j++) {
247             final Node dependencyNode = dependencyNodes.item(j);
248             if ("dependency".equals(dependencyNode.getNodeName())) {
249                 final NodeList childDependencyNodes = dependencyNode.getChildNodes();
250                 final MavenArtifact dependency = new MavenArtifact();
251                 String scope = null;
252                 String optional = null;
253                 for (int k = 0; k < childDependencyNodes.getLength(); k++) {
254                     final Node childDependencyNode = childDependencyNodes.item(k);
255                     final String nodeName = childDependencyNode.getNodeName();
256                     if ("groupId".equals(nodeName)) {
257                         dependency.groupId = childDependencyNode.getTextContent();
258                     } else if ("artifactId".equals(nodeName)) {
259                         dependency.artifactId = childDependencyNode.getTextContent();
260                     } else if ("version".equals(nodeName)) {
261                         dependency.version = childDependencyNode.getTextContent();
262                     } else if ("scope".equals(nodeName)) {
263                         scope = childDependencyNode.getTextContent();
264                     } else if ("optional".equals(nodeName)) {
265                         optional = childDependencyNode.getTextContent();
266                     }
267                 }
268                 if ((scope == null || "compile".equals(scope)) && !"true".equals(optional)) {
269                     deps.add(dependency);
270                 }
271             }
272         }
273         return deps;
274     }
275
276     private void update() throws IOException {
277         if (!updated && version != null) {
278             updated = true;
279             final String filePath = getPath(".pom");
280             final File pomXml = getMavenArtifact(filePath);
281             if (pomXml != null) {
282                 try (InputStream input = new FileInputStream(pomXml)) {
283                     parsePomXml(input);
284                 }
285             }
286         }
287     }
288
289     public String getGroupId() {
290         return groupId;
291     }
292
293     public String getArtifactId() {
294         return artifactId;
295     }
296
297     public String getVersion() {
298         return version;
299     }
300
301     public String getName() throws IOException {
302         if (name == null && parent != null) {
303             parent.update();
304             return parent.getName();
305         }
306         return name;
307     }
308
309     public String getUrl() throws IOException {
310         if (url == null && parent != null) {
311             parent.update();
312             return parent.getUrl();
313         }
314         return url;
315     }
316
317     public Map<String, String> getLicenseUrlsByName() throws IOException {
318         if (licenseUrlsByName.isEmpty() && parent != null) {
319             parent.update();
320             return parent.getLicenseUrlsByName();
321         }
322         return licenseUrlsByName;
323     }
324
325     // not needed:
326     //    MavenArtifact getParent() {
327     //        return parent;
328     //    }
329     //
330     //    List<MavenArtifact> getDependencies() {
331     //        return dependencies;
332     //    }
333     //
334     //    List<MavenArtifact> getManagedDependencies() {
335     //        return managedDependencies;
336     //    }
337
338     private List<MavenArtifact> getAllManagedDependencies() throws IOException {
339         update();
340         final List<MavenArtifact> allManagedDependencies = new ArrayList<>(managedDependencies);
341         if (parent != null) {
342             allManagedDependencies.addAll(parent.getAllManagedDependencies());
343         }
344         return allManagedDependencies;
345     }
346
347     List<MavenArtifact> getAllDependencies() throws IOException {
348         return getAllDependencies(1);
349     }
350
351     private List<MavenArtifact> getAllDependencies(int level) throws IOException {
352         if (level > 10) {
353             // limit recursivity against cycle (for example dom4j 1.5.2 <-> jaxen 1.1-beta-4)
354             return Collections.emptyList();
355         }
356         // update dependencies if needed
357         update();
358         final List<MavenArtifact> transitiveDependencies = new ArrayList<>();
359         final List<MavenArtifact> allManagedDependencies = getAllManagedDependencies();
360         for (final MavenArtifact dependency : dependencies) {
361             if (dependency.version == null) {
362                 for (final MavenArtifact managedDependency : allManagedDependencies) {
363                     if (dependency.isSame(managedDependency)) {
364                         dependency.version = managedDependency.version;
365                         break;
366                     }
367                 }
368             }
369             transitiveDependencies.addAll(dependency.getAllDependencies(level + 1));
370         }
371         if (parent != null) {
372             transitiveDependencies.addAll(parent.getAllDependencies(level + 1));
373         }
374
375         final List<MavenArtifact> allDependencies = new ArrayList<>(dependencies);
376         for (final MavenArtifact transitiveDependency : transitiveDependencies) {
377             if (!transitiveDependency.isContained(allDependencies)) {
378                 allDependencies.add(transitiveDependency);
379             }
380         }
381         return allDependencies;
382     }
383
384     private boolean isContained(List<MavenArtifact> artifacts) {
385         for (final MavenArtifact artifact : artifacts) {
386             if (isSame(artifact)) {
387                 return true;
388             }
389         }
390         return false;
391     }
392
393     private boolean isSame(MavenArtifact artifact) {
394         return groupId.equals(artifact.groupId) && artifactId.equals(artifact.artifactId);
395     }
396
397     private String getPath(String extension) {
398         return groupId.replace('.', '/') + '/' + artifactId + '/' + version + '/' + artifactId + '-'
399                 + version + extension;
400     }
401
402     public static Map<String, MavenArtifact> getWebappDependencies() throws IOException {
403         // list dependencies in WEB-INF/lib
404         // and read names, urls, licences in META-INF/maven/.../pom.xml from jar files when available
405         final Map<String, MavenArtifact> webappDependencies = getWebappDependenciesFromWebInfLib();
406
407         // when pom.xml not available in some jar files,
408         // list all dependencies in webapp's pom.xml if it exists or in the other dependencies' pom.xml,
409         // including transitive dependencies
410         final List<MavenArtifact> allDependencies = new ArrayList<>(
411                 getWebappDependenciesFromPomXml());
412         for (final MavenArtifact dependency : webappDependencies.values()) {
413             if (dependency != null && !dependency.isContained(allDependencies)) {
414                 allDependencies.add(dependency);
415                 for (final MavenArtifact transitiveDependency : dependency.getAllDependencies()) {
416                     if (!transitiveDependency.isContained(allDependencies)) {
417                         allDependencies.add(transitiveDependency);
418                     }
419                 }
420             }
421         }
422         // in order to complete names, urls, licences from all dependencies and parents
423         for (final Map.Entry<String, MavenArtifact> entry : webappDependencies.entrySet()) {
424             if (entry.getValue() == null) {
425                 final String jarFileName = entry.getKey();
426                 for (final MavenArtifact dependency : allDependencies) {
427                     if (jarFileName.startsWith(
428                             dependency.getArtifactId() + '-' + dependency.getVersion())) {
429                         entry.setValue(dependency);
430                         break;
431                     }
432                 }
433             }
434         }
435         return webappDependencies;
436     }
437
438     private static List<MavenArtifact> getWebappDependenciesFromPomXml() throws IOException {
439         final InputStream webappPomXmlAsStream = getWebappPomXmlAsStream();
440         if (webappPomXmlAsStream != null) {
441             try {
442                 final MavenArtifact webappArtifact = new MavenArtifact();
443                 webappArtifact.parsePomXml(webappPomXmlAsStream);
444                 return webappArtifact.getAllDependencies();
445             } finally {
446                 webappPomXmlAsStream.close();
447             }
448         }
449         return Collections.emptyList();
450     }
451
452     private static Map<String, MavenArtifact> getWebappDependenciesFromWebInfLib()
453             throws IOException {
454         final ServletContext servletContext = Parameters.getServletContext();
455         final String directory = "/WEB-INF/lib/";
456
457         final Set<String> dependencies = servletContext.getResourcePaths(directory);
458         // If needed, catch Exception again because Tomcat 8 can throw
459         // "IllegalStateException: The resources may not be accessed if they are not currently started"
460         // for some ServletContext states (issue 415)
461         if (dependencies == null || dependencies.isEmpty()) {
462             return Collections.emptyMap();
463         }
464         final Map<String, MavenArtifact> result = new TreeMap<>();
465         for (final String dependency : dependencies) {
466             if (dependency.endsWith(".jar") || dependency.endsWith(".JAR")) {
467                 final String fileName = dependency.substring(directory.length());
468                 final URL jarFileLocation = servletContext.getResource(dependency);
469                 if (jarFileLocation != null) {
470                     result.put(fileName, parseDependency(jarFileLocation));
471                 } else {
472                     result.put(fileName, null);
473                 }
474             }
475         }
476         return result;
477     }
478
479     public static InputStream getWebappPomXmlAsStream() {
480         return getWebappPomFile("pom.xml");
481     }
482
483     public static synchronized String getWebappVersion() {
484         if (webappVersion == null) {
485             webappVersion = Parameter.APPLICATION_VERSION.getValue();
486             if (webappVersion == null) {
487                 final InputStream input = getWebappPomFile("pom.properties");
488                 if (input != null) {
489                     try {
490                         try {
491                             final Properties properties = new Properties();
492                             properties.load(input);
493                             webappVersion = properties.getProperty("version");
494                         } finally {
495                             input.close();
496                         }
497                     } catch (final IOException e) {
498                         LOG.debug(e.toString(), e);
499                     }
500                 }
501                 if (webappVersion == null) {
502                     // remember that the webapp version can't be found
503                     webappVersion = "";
504                 }
505             }
506         }
507         if (webappVersion.isEmpty()) {
508             return null;
509         }
510         return webappVersion;
511     }
512
513     private static InputStream getWebappPomFile(String pomFilename) {
514         final Set<?> mavenDir = Parameters.getServletContext().getResourcePaths("/META-INF/maven/");
515         if (mavenDir == null || mavenDir.isEmpty()) {
516             return null;
517         }
518         final Set<?> groupDir = Parameters.getServletContext()
519                 .getResourcePaths((String) mavenDir.iterator().next());
520         if (groupDir == null || groupDir.isEmpty()) {
521             return null;
522         }
523         final InputStream pomXml = Parameters.getServletContext()
524                 .getResourceAsStream(groupDir.iterator().next() + pomFilename);
525         if (pomXml == null) {
526             return null;
527         }
528         return new BufferedInputStream(pomXml);
529     }
530
531     public static File getSourceJarFile(URL classesJarFileUrl) throws IOException {
532         final String file = classesJarFileUrl.getFile();
533         if (file.endsWith(".jar")) {
534             final File sources = new File(file.replace(".jar""-sources.jar"));
535             if (sources.exists()) {
536                 return sources;
537             }
538         }
539         final byte[] pomProperties = readMavenFileFromJarFile(classesJarFileUrl, "pom.properties");
540         if (pomProperties == null) {
541             final Map<String, String> sourceFilePaths = getSourceFilePathsByJarFileNames();
542             String jarFileName = file;
543             if (jarFileName.endsWith("!/")) {
544                 // remove "!/" at the end, for spring-boot launched with "java -jar"
545                 jarFileName = jarFileName.substring(0, jarFileName.length() - "!/".length());
546             }
547             jarFileName = jarFileName.substring(jarFileName.lastIndexOf('/') + 1);
548             final String sourceFilePath = sourceFilePaths.get(jarFileName);
549             if (sourceFilePath != null) {
550                 return getMavenArtifact(sourceFilePath);
551             }
552             return null;
553         }
554         final Properties properties = new Properties();
555         properties.load(new ByteArrayInputStream(pomProperties));
556         final MavenArtifact mavenArtifact = new MavenArtifact();
557         mavenArtifact.groupId = properties.getProperty("groupId");
558         mavenArtifact.artifactId = properties.getProperty("artifactId");
559         mavenArtifact.version = properties.getProperty("version");
560         final String filePath = mavenArtifact.getPath("-sources.jar");
561         return getMavenArtifact(filePath);
562     }
563
564     private static synchronized Map<String, String> getSourceFilePathsByJarFileNames()
565             throws IOException {
566         if (sourceFilePathsByJarFileNames == null) {
567             final Map<String, MavenArtifact> webappDependencies = getWebappDependencies();
568             final Map<String, String> sourceFilePaths = new HashMap<>();
569             for (final Map.Entry<String, MavenArtifact> entry : webappDependencies.entrySet()) {
570                 final String jarFileName = entry.getKey();
571                 final MavenArtifact dependency = entry.getValue();
572                 if (dependency != null) {
573                     final String filePath = dependency.getPath("-sources.jar");
574                     sourceFilePaths.put(jarFileName, filePath);
575                 }
576             }
577             sourceFilePathsByJarFileNames = sourceFilePaths;
578         }
579         return sourceFilePathsByJarFileNames;
580     }
581
582     private static File getMavenArtifact(String filePath) {
583         if (filePath.contains("${")) {
584             // si le chemin contient des variables non résolues telles que ${project.version},
585             // ce n'est pas la peine de chercher
586             return null;
587         }
588         final File storageDirectory = Parameters
589                 .getStorageDirectory(Parameters.getCurrentApplication());
590         final String subDirectory;
591         if (filePath.endsWith(".pom")) {
592             subDirectory = "poms";
593         } else {
594             subDirectory = "sources";
595         }
596         final File file = new File(storageDirectory,
597                 subDirectory + '/' + filePath.substring(filePath.lastIndexOf('/') + 1));
598         if (!file.exists() || file.length() == 0) {
599             for (final String mavenRepository : getMavenRepositories()) {
600                 final String url = mavenRepository + '/' + filePath;
601                 if (!url.startsWith("http")) {
602                     if (!new File(url).exists()) {
603                         continue;
604                     }
605                     return new File(url);
606                 }
607                 mkdirs(file.getParentFile());
608                 try (OutputStream output = new FileOutputStream(file)) {
609                     final LabradorRetriever labradorRetriever = new LabradorRetriever(new URL(url));
610                     labradorRetriever.downloadTo(output);
611                     // si trouvé, on arrête
612                     break;
613                 } catch (final IOException e) {
614                     InputOutput.deleteFile(file);
615                     // si non trouvé, on continue avec le repo suivant s'il y en a un
616                 }
617             }
618         }
619         if (file.exists()) {
620             return file;
621         }
622         return null;
623     }
624
625     public static File getTomcatSrcZipFile() {
626         final String serverInfo = Parameters.getServletContext().getServerInfo();
627         if (!serverInfo.matches("Apache Tomcat/\\d+\\.\\d+\\.\\d+")) {
628             // si pas Tomcat ou si Tomcat version x.0.0.My, tant pis
629             return null;
630         }
631         final String version = serverInfo.substring(serverInfo.lastIndexOf('/') + 1);
632         final String fileName = "apache-tomcat-" + version + "-src.zip";
633         final File storageDirectory = Parameters
634                 .getStorageDirectory(Parameters.getCurrentApplication());
635         final String subDirectory = "sources";
636         final File file = new File(storageDirectory, subDirectory + '/' + fileName);
637         if (!file.exists() || file.length() == 0) {
638             final String majorVersion = version.substring(0, version.indexOf('.'));
639             final String url = TOMCAT_ARCHIVES + "tomcat-" + majorVersion + "/v" + version + "/src/"
640                     + fileName;
641             mkdirs(file.getParentFile());
642             try (OutputStream output = new FileOutputStream(file)) {
643                 final LabradorRetriever labradorRetriever = new LabradorRetriever(new URL(url));
644                 labradorRetriever.downloadTo(output);
645             } catch (final IOException e) {
646                 InputOutput.deleteFile(file);
647             }
648         }
649         if (file.exists()) {
650             return file;
651         }
652         return null;
653     }
654
655     private static void mkdirs(File directory) {
656         if (!directory.exists() && !directory.mkdirs()) {
657             throw new IllegalStateException("Can't create directory " + directory.getPath());
658         }
659     }
660
661     private static byte[] readMavenFileFromJarFile(URL jarFileLocation, String pomFileName)
662             throws IOException {
663         try (ZipInputStream zipInputStream = new ZipInputStream(
664                 new BufferedInputStream(jarFileLocation.openStream(), 4096))) {
665             ZipEntry entry = zipInputStream.getNextEntry();
666             while (entry != null) {
667                 if (entry.getName().startsWith("META-INF/maven/")
668                         && entry.getName().endsWith("/" + pomFileName)) {
669                     return InputOutput.pumpToByteArray(zipInputStream);
670                 }
671                 zipInputStream.closeEntry();
672                 entry = zipInputStream.getNextEntry();
673             }
674         }
675         return null;
676     }
677
678     private static List<String> getMavenRepositories() {
679         final String parameter = Parameter.MAVEN_REPOSITORIES.getValue();
680         if (parameter != null) {
681             final List<String> result = new ArrayList<>();
682             for (final String repo : parameter.split(",")) {
683                 result.add(repo.trim());
684             }
685             return result;
686         }
687         return Arrays.asList(LOCAL_REPO.getPath(), MAVEN_CENTRAL);
688     }
689
690     private static String replaceProperty(String value, Map<String, String> properties) {
691         if (value != null && value.startsWith("${") && value.endsWith("}")) {
692             final String propertyName = value.substring("${".length(),
693                     value.length() - "}".length());
694             final String propertyValue = properties.get(propertyName);
695             if (propertyValue != null) {
696                 return propertyValue;
697             }
698         }
699         return value;
700     }
701
702     @Override
703     public String toString() {
704         return getClass().getSimpleName() + '[' + groupId + ':' + artifactId + ':' + version + ']';
705     }
706 }
707