/*
* 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();
}
}
}