/* * Copyright 2012 Kantega AS * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.kantega.revoc.source; import org.kantega.revoc.config.Config; import org.apache.commons.io.IOUtils; import org.w3c.dom.*; import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.io.*; import java.net.*; import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.zip.ZipEntry; /** * */ public class MavenSourceArtifactSourceSource implements SourceSource { private final Map<String, MavenSourceInfo> mavenInfoMap = new HashMap<String, MavenSourceInfo>(); private final Map<String, JarFile> sourceFileMap = new HashMap<String, JarFile>(); private File mavenDownloads; private String mavenRepo; public MavenSourceArtifactSourceSource() { String mavenDownloadsEnv = Config.getProperty("REVOC_MAVEN_DOWNLOAD"); String mavenRepo = Config.getProperty("REVOC_MAVEN_REPO"); if(mavenDownloadsEnv != null && mavenRepo != null) { File mavenDir = new File(mavenDownloadsEnv); mavenDir.mkdirs(); this.mavenDownloads = mavenDir; if(!mavenRepo.endsWith("/")) { mavenRepo +="/"; } this.mavenRepo = mavenRepo; } } public String[] getSource(String className, ClassLoader classLoader) { URL resource = classLoader.getResource(className + ".class"); String filePath = getFilePath(resource); if (filePath == null) { return null; } try { MavenSourceInfo info; synchronized (mavenInfoMap) { if (!mavenInfoMap.containsKey(filePath)) { mavenInfoMap.put(filePath, parseInfo(filePath, resource)); } info = mavenInfoMap.get(filePath); } JarFile sourceFile; synchronized (sourceFileMap) { if(!sourceFileMap.containsKey(filePath)) { sourceFileMap.put(filePath, getSourceFile(filePath, info)); } sourceFile = sourceFileMap.get(filePath); } if(sourceFile == null) { return null; } String sourcePath = className.replace('.', '/') + ".java"; ZipEntry entry = sourceFile.getEntry(sourcePath); if (entry == null) { return null; } InputStream inputStream = sourceFile.getInputStream(entry); List<String> lines = IOUtils.readLines(inputStream); return lines.toArray(new String[lines.size()]); } catch (IOException e) { throw new RuntimeException(e); } } private JarFile getSourceFile(String filePath, MavenSourceInfo info) throws IOException { if(!filePath.endsWith(".jar")) { return null; } File sourceJar; if(info == null) { sourceJar = new File(filePath.substring(0, filePath.lastIndexOf(".jar")) +"-sources.jar"); } else { File m2Repo = new File(System.getProperty("user.home"), ".m2/repository"); sourceJar = new File(m2Repo, getSourceFileMavenPath(info)); if (!sourceJar.exists()) { sourceJar = new File(new File(filePath).getParentFile(), getSourceArtifactName(info)); } } if (!sourceJar.exists()) { if(mavenDownloads != null && info != null) { File downLoad = downloadMavenSource(info); if(downLoad != null) { sourceJar = downLoad; } } if(!sourceJar.exists()) { return null; } } return new JarFile(sourceJar); } private File downloadMavenSource(MavenSourceInfo info) { File downloadFile = new File(mavenDownloads, getSourceFileMavenPath(info)); if(info.getVersion().contains("-SNAPSHOT")) { downloadFile.delete(); } if(downloadFile.exists()) { return downloadFile; } downloadFile.getParentFile().mkdirs(); File tempFile = new File(downloadFile.getParentFile(), "_tmp" + downloadFile.getName()); try { URL remoteSourceURL = new URL(this.mavenRepo + getSourceFileDownloadMavenPath(info)); InputStream stream = remoteSourceURL.openStream(); FileOutputStream output = new FileOutputStream(tempFile); IOUtils.copy(stream, output); output.close(); tempFile.renameTo(downloadFile); return downloadFile; } catch (MalformedURLException e) { return null; } catch (IOException e) { return null; } } private String getSourceFileMavenPath(MavenSourceInfo info) { StringBuilder sb = new StringBuilder(); appendArtifactDirectory(info, sb); sb.append(getSourceArtifactName(info)); return sb.toString(); } private String getSourceFileDownloadMavenPath(MavenSourceInfo info) { StringBuilder sb = new StringBuilder(); appendArtifactDirectory(info, sb); if(info.getVersion().contains("SNAPSHOT")) { sb.append(resolveSnapshotFileName(info, sb.toString())); } else { sb.append(getSourceArtifactName(info)); } return sb.toString(); } private void appendArtifactDirectory(MavenSourceInfo info, StringBuilder sb) { sb.append(info.getGroupId().replace('.', '/')).append("/"); sb.append(info.getArtifactId()).append("/"); sb.append(info.getVersion()).append("/"); } private String resolveSnapshotFileName(MavenSourceInfo info, String artifactUrl) { try { URL url = new URL(this.mavenRepo + artifactUrl + "maven-metadata.xml"); final DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.addRequestProperty("Cache-Control", "max-age=0"); urlConnection.addRequestProperty("User-Agent", "Apache-Maven/3.0"); InputStream inputStream = urlConnection.getInputStream(); final Document doc = builder.parse(inputStream); inputStream.close(); Element versioning = (Element) doc.getDocumentElement().getElementsByTagName("versioning").item(0); Element snapshotVersions = (Element) versioning.getElementsByTagName("snapshotVersions").item(0); NodeList snapshotVersion = snapshotVersions.getElementsByTagName("snapshotVersion"); for(int i = 0; i < snapshotVersion.getLength(); i++) { final Element versioningElement = (Element) snapshotVersion.item(i); String value= getText((Element) versioningElement.getElementsByTagName("value").item(0)); return info.getArtifactId() + "-" + value + "-sources.jar"; } return null; } catch (ParserConfigurationException e) { throw new RuntimeException(e); } catch (SAXException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } } private String getText(Element versionNode) { StringBuilder text = new StringBuilder(); for (int t = 0; t < versionNode.getChildNodes().getLength(); t++) { final Node node = versionNode.getChildNodes().item(t); if (node instanceof Text) { text.append(((Text) node).getData()); } } return text.toString(); } private String getSourceArtifactName(MavenSourceInfo info) { StringBuilder sb = new StringBuilder(); sb.append(info.getArtifactId()).append("-").append(info.getVersion()).append("-sources.jar"); return sb.toString(); } private String getFilePath(URL resource) { if (resource == null) { return null; } if (!resource.getProtocol().equals("jar")) { return null; } String filePath = resource.getPath(); if (!filePath.startsWith("file:") || !filePath.contains("!")) { return null; } filePath = filePath.substring("file:".length()); return URLDecoder.decode(filePath.substring(0, filePath.indexOf("!"))); } private MavenSourceInfo parseInfo(String filePath, URL resource) { File file = new File(filePath); JarFile jarFile = null; boolean isNewFile = false; try { URLConnection urlConnection = resource.openConnection(); if (urlConnection instanceof JarURLConnection) { jarFile = ((JarURLConnection) urlConnection).getJarFile(); } else { jarFile = new JarFile(file); isNewFile = true; } Enumeration<JarEntry> entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); String prefix = "META-INF/maven/"; String propSuffix = "/pom.properties"; if (entry.getName().startsWith(prefix) && entry.getName().endsWith(propSuffix)) { Properties props = new Properties(); InputStream inputStream = jarFile.getInputStream(entry); props.load(inputStream); inputStream.close(); String groupId = props.getProperty("groupId"); String artifactId = props.getProperty("artifactId"); String version = props.getProperty("version"); if (file.getName().startsWith(artifactId + "-" + version)) { return new MavenSourceInfo(groupId, artifactId, version); } } } return null; } catch (IOException e) { throw new RuntimeException(e); } finally { if (isNewFile) { if (jarFile != null) { try { jarFile.close(); } catch (IOException e) { throw new RuntimeException(e); } } } } } private class MavenSourceInfo { private final String groupId; private final String artifactId; private final String version; private JarFile sourceFile; public MavenSourceInfo(String groupId, String artifactId, String version) { this.groupId = groupId; this.artifactId = artifactId; this.version = version; } public String getArtifactId() { return artifactId; } public String getGroupId() { return groupId; } public String getVersion() { return version; } public void setSourceFile(JarFile sourceFile) { this.sourceFile = sourceFile; } public JarFile getSourceFile() { return sourceFile; } } public static void main(String[] args) throws IOException { URL url = new URL("http://nexus.kantega.lan/content/repositories/snapshots/no/kantega/davexchange/1.6.16-SNAPSHOT/maven-metadata.xml"); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.addRequestProperty("Cache-Control", "max-age=0"); urlConnection.addRequestProperty("User-Agent", "Apache-Maven/3.0"); InputStream inputStream = urlConnection.getInputStream(); String content = IOUtils.toString(inputStream); System.out.println(content); } }