/* * Copyright 2000-2011 JetBrains s.r.o. * * 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 jetbrains.buildServer.tools; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.io.PrintWriter; import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; import jetbrains.buildServer.core.runtime.IProgressMonitor; import jetbrains.buildServer.core.runtime.RuntimeUtil; import jetbrains.buildServer.util.FileUtil; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.io.output.NullWriter; import org.apache.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.w3c.tidy.Tidy; import com.intellij.openapi.util.JDOMUtil; public class JDKTools { public static class JDKUrlResolver { private static String JAVA6_DOWNLOADS_PAGE_URL = "http://download.java.net/jdk6/source/"; private static Pattern JAVA6_VERSION_PATTERN = Pattern.compile("(.*)Java Platform, Standard Edition (.*?) Source Snapshot Releases(.*)", Pattern.MULTILINE | Pattern.DOTALL); private static String JAVA6_VERSION_CHECKER_URL_PATTERN = "https://cds.sun.com/is-bin/INTERSHOP.enfinity/WFS/CDS-CDS_Developer-Site/en_US/-/USD/ViewProductDetail-Start?ProductRef=jdk-%s-oth-JPR@CDS-CDS_Developer"; private static Logger LOG = Logger.getLogger(JDKUrlResolver.class); public JDKUrlResolver() { } public static URL getLatestJDK() throws IOException { final HttpClient client = new HttpClient(); final GetMethod method = new GetMethod(JAVA6_DOWNLOADS_PAGE_URL); client.executeMethod(method); try { return getLatestProduct(client, asString(method.getResponseBodyAsStream())); } finally { method.releaseConnection(); } } private static URL getLatestProduct(final @NotNull HttpClient client, final @NotNull String body) throws IOException { final Matcher matcher = JAVA6_VERSION_PATTERN.matcher(body); if (matcher.matches()) { final String checkVersion = matcher.group(2); final String checkURL = String.format(JAVA6_VERSION_CHECKER_URL_PATTERN, checkVersion); LOG.debug(String.format("Checking URL: %s", checkURL)); final GetMethod checkMethod = new GetMethod(checkURL); try { int response = client.executeMethod(checkMethod); if (200 == response) { final Header cookie = checkMethod.getResponseHeader("Set-Cookie"); if (cookie != null && cookie.getValue().contains("CDS_DETECT")) { return new URL(checkURL); } else { LOG.debug(String.format("Wrong 'Set-Cookie' header: '%s'", cookie)); } } else { LOG.debug(String.format("Server returned wrong code '%d'", response)); } } finally { checkMethod.releaseConnection(); } } return null; } public static boolean accept(final @NotNull URL url) { if ("cds.sun.com".equals(url.getHost()) && url.getPath().startsWith("/is-bin/INTERSHOP.enfinity/WFS/CDS-CDS_Developer-Site")) { return true; } return false; } public static String asString(final @NotNull InputStream in) throws IOException { final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); BufferedOutputStream bufferedStream = new BufferedOutputStream(byteArrayOutputStream); FileUtil.copyStreams(new BufferedInputStream(in), bufferedStream); final String out = new String(byteArrayOutputStream.toByteArray()); bufferedStream.close(); return out; } public static URL resolve(URL url, IProgressMonitor monitor) throws Exception { final HttpURLConnection stageOne = traverseDownloadPage(url, Platform2.current(), CPU.current()); final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); FileUtil.copyStreams(new BufferedInputStream(stageOne.getInputStream()), new BufferedOutputStream(byteArrayOutputStream)); final String page = new String(byteArrayOutputStream.toByteArray()); final URL target = parseDownloadResponse(page); LOG.debug(String.format("Resolved: %s", target)); return target; } private static HttpURLConnection traverseDownloadPage(URL url, Platform2 platform, CPU cpu) throws Exception { String cookie; org.jdom.Element jForm; try { HttpURLConnection con = (HttpURLConnection) url.openConnection(); cookie = con.getHeaderField("Set-Cookie"); LOG.debug("Cookie=" + cookie); Tidy tidy = new Tidy(); tidy.setXmlOut(true); tidy.setErrout(new PrintWriter(new NullWriter())); final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); tidy.parse(con.getInputStream(), buffer); String content = buffer.toString(); final org.jdom.Document jdoc = JDOMUtil.loadDocument(content); final List jnodes = (List) org.jdom.xpath.XPath.selectNodes(jdoc, "//form"); jForm = null; for (org.jdom.Element jElement : jnodes) { String action = jElement.getAttributeValue("action"); LOG.debug("Found form:" + action); if (action.contains("ViewFilteredProducts")) { jForm = jElement; break; } } } catch (IOException e) { throw new Exception("Failed to access " + url, e); } url = new URL(jForm.getAttributeValue("action")); try { HttpURLConnection con = (HttpURLConnection) url.openConnection();//(HttpURLConnection) ProxyConfiguration.open(url); con.setRequestMethod("POST"); con.setDoOutput(true); con.setRequestProperty("Cookie", cookie); con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); PrintStream os = new PrintStream(con.getOutputStream()); // select platform String primary = null, secondary = null; final org.jdom.Element jp = (org.jdom.Element) org.jdom.xpath.XPath.selectSingleNode(jForm, ".//select[@id='dnld_platform']"); for (org.jdom.Element jopt : (List) jp.getChildren("option")) { String value = jopt.getAttributeValue("value"); String vcap = value.toUpperCase(Locale.ENGLISH); if (!platform.is(vcap)) continue; switch (cpu.accept(vcap)) { case PRIMARY: primary = value; break; case SECONDARY: secondary = value; break; case UNACCEPTABLE: break; } } if (primary == null) primary = secondary; if (primary == null) throw new IllegalArgumentException("Couldn't find the right download for " + platform + " and " + cpu + " combination"); os.print(/*p.attributeValue*/jp.getAttributeValue("name") + '=' + primary); LOG.debug("Platform choice:" + primary); // select language org.jdom.Element jl = (org.jdom.Element) org.jdom.xpath.XPath.selectSingleNode(jForm, ".//select[@id='dnld_language']"); if (jl != null) { os.print("&" + jl.getAttributeValue("name") + "=" + jl.getChild("option").getAttributeValue("value")); } for (org.jdom.Element je : (List) org.jdom.xpath.XPath.selectNodes(jForm, ".//input")) { os.print('&'); os.print(je.getAttributeValue("name")); os.print('='); String value = je.getAttributeValue("value"); if (value == null) os.print("on"); // assume this is a checkbox else os.print(URLEncoder.encode(value, "UTF-8")); } os.close(); return con; } catch (IOException e) { throw new Exception("Failed to access " + url, e); } } private static URL parseDownloadResponse(/*TaskListener log, */String page) throws IOException { Pattern HREF = Pattern.compile(" that confuses dom4j/jtidy LOG.debug("Choosing the download bundle"); List urls = new ArrayList(); while (m.find()) { String url = m.group(1); LOG.debug("Considering a download link:" + url); // still more options to choose from. // avoid rpm bundles, and avoid tar.Z bundle if (url.contains("rpm")) continue; if (url.contains("tar.Z")) continue; // sparcv9 bundle is add-on to the sparc bundle, so just download 32bit sparc bundle, even on 64bit system if (url.contains("sparcv9")) continue; urls.add(url); LOG.debug("Found a download candidate: " + url); } if (urls.isEmpty()) { throw new IOException("found no matches in: " + page); } // prefer the first match because sometimes "optional downloads" follow the main bundle return new URL(urls.get(0)); } private static enum Preference { PRIMARY, SECONDARY, UNACCEPTABLE } /** * Supported platform. */ private static enum Platform2 { LINUX("jdk.sh"), SOLARIS("jdk.sh"), WINDOWS("jdk.exe"); /** * Choose the file name suitable for the downloaded JDK bundle. */ public final String bundleFileName; Platform2(String bundleFileName) { this.bundleFileName = bundleFileName; } public boolean is(String line) { return line.contains(name()); } public static Platform2 current() throws Exception { String arch = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); if (arch.contains("linux")) return LINUX; if (arch.contains("windows")) return WINDOWS; if (arch.contains("sun") || arch.contains("solaris")) return SOLARIS; throw new IllegalArgumentException/*DetectionFailedException*/("Unknown CPU name: " + arch); } } /** * CPU type. */ private static enum CPU { i386, amd64, Sparc, Itanium; /** * In JDK5u3, I see platform like "Linux AMD64", while JDK6u3 refers to * "Linux x64", so just use "64" for locating bits. */ public Preference accept(String line) { switch (this) { // these two guys are totally incompatible with everything else, so no fallback case Sparc: return must(line.contains("SPARC")); case Itanium: return must(line.contains("ITANIUM")); // 64bit Solaris, Linux, and Windows can all run 32bit executable, so fall back to 32bit if 64bit bundle is not found case amd64: if (line.contains("64")) return Preference.PRIMARY; if (line.contains("SPARC") || line.contains("ITANIUM")) return Preference.UNACCEPTABLE; return Preference.SECONDARY; case i386: if (line.contains("64") || line.contains("SPARC") || line.contains("ITANIUM")) return Preference.UNACCEPTABLE; return Preference.PRIMARY; } return Preference.UNACCEPTABLE; } private static Preference must(boolean b) { return b ? Preference.PRIMARY : Preference.UNACCEPTABLE; } public static CPU current() throws Exception { String arch = System.getProperty("os.arch").toLowerCase(Locale.ENGLISH); if (arch.contains("sparc")) return Sparc; if (arch.contains("ia64")) return Itanium; if (arch.contains("amd64") || arch.contains("86_64")) return amd64; if (arch.contains("86")) return i386; throw new Exception("Unknown CPU architecture: " + arch); } } } public static class LastJDKVersionProvider implements jetbrains.buildServer.core.runtime.osgx.ToolRepository.IToolVersionProvider { private static Logger LOG = Logger.getLogger(LastJDKVersionProvider.class); private static final Pattern URL_VERSION_PATTERN = Pattern.compile("(.*)cds.sun.com/is-bin/INTERSHOP.enfinity/WFS/CDS-CDS_Developer-Site/(.*)/ViewProductDetail-Start\\?ProductRef=jdk-(.*?)-(.*)@CDS-CDS_Developer"); private static final Pattern VERSION_FORMAT_PATTERN = Pattern.compile("(\\d*)(.*?)(\\d*?)"); private String myVersion; private URI myLocation; private void load() { try { myLocation = JDKUrlResolver.getLatestJDK().toURI(); final String latestJdk = myLocation.toString(); final Matcher matcher = URL_VERSION_PATTERN.matcher(latestJdk); if (matcher.matches()) { myVersion = buildVersion(matcher.group(3)); } } catch (Exception e) { LOG.error(e.getMessage(), e); } } String buildVersion(String rawVersion) { final Matcher matcher = VERSION_FORMAT_PATTERN.matcher(rawVersion); if (matcher.matches()) { String minor = matcher.group(1); String micro = matcher.group(2); String update = matcher.group(3); return String.format("1.%s.%s_%s", minor, "u".equals(micro) ? 0 : micro, update); } return rawVersion; } @Override public String toString() { return getLocation().toString(); } public String getVersion() { if (myVersion == null) { load(); } return myVersion; } public URI getLocation() { if (myLocation == null) { load(); } return myLocation; } } public static class JDKUrlInterceptor { private final URL myURL; public JDKUrlInterceptor(final URL url) { myURL = url; } @Override public String toString() { if (JDKUrlResolver.accept(myURL)) { try { return JDKUrlResolver.resolve(myURL, RuntimeUtil.NULL_MONITOR).toString(); } catch (Exception e) { throw new RuntimeException(e); } } return myURL.toString(); } } }