Java tutorial
/******************************************************************************* * Copyright (c) 2013 Cloud Bees, Inc. * All rights reserved. * This program is made available under the terms of the * Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Cloud Bees, Inc. - initial API and implementation *******************************************************************************/ package com.cloudbees.eclipse.core; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Scanner; import org.apache.http.Header; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HTTP; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.SubProgressMonitor; import com.cloudbees.eclipse.core.domain.JenkinsInstance; import com.cloudbees.eclipse.core.jenkins.api.JenkinsBuildDetailsResponse; import com.cloudbees.eclipse.core.jenkins.api.JenkinsConfigParser; import com.cloudbees.eclipse.core.jenkins.api.JenkinsConsoleLogResponse; import com.cloudbees.eclipse.core.jenkins.api.JenkinsInstanceResponse; import com.cloudbees.eclipse.core.jenkins.api.JenkinsJobAndBuildsResponse; import com.cloudbees.eclipse.core.jenkins.api.JenkinsJobsResponse; import com.cloudbees.eclipse.core.jenkins.api.JenkinsScmConfig; import com.cloudbees.eclipse.core.util.Utils; import com.google.gson.Gson; /** * Service to access Jenkins instances * * @author ahti */ public class JenkinsService { enum ResponseType { STRING, STREAM, HTTP } /*private String url; private String label;*/ private final JenkinsInstance jenkins; private final Map<String, JenkinsScmConfig> scms = new HashMap<String, JenkinsScmConfig>(); public JenkinsService(final JenkinsInstance jenkins) { this.jenkins = jenkins; } /** * @param viewUrl * full url for the view. <code>null</code> if all jobs from this instance. * @return * @throws CloudBeesException */ public JenkinsJobsResponse getJobs(final String viewUrl, final IProgressMonitor monitor) throws CloudBeesException { assertCorrectUrl(viewUrl); try { monitor.beginTask("Fetching Job list for '" + this.jenkins.label + "'...", 10); Gson g = Utils.createGson(); String reqUrl = viewUrl; // != null ? viewUrl : jenkins.url; if (!reqUrl.endsWith("/")) { reqUrl += "/"; } String uri = reqUrl + "api/json"; HttpPost post = new HttpPost(uri); //post.setHeader("Accept", "application/json"); post.setHeader("Content-type", "application/x-www-form-urlencoded"); List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2); nameValuePairs.add(new BasicNameValuePair("tree", JenkinsJobsResponse.QTREE)); //StringEntity se = new StringEntity("tree="+JenkinsJobsResponse.QTREE, "UTF-8"); //post.setEntity(se); post.setEntity(new UrlEncodedFormEntity(nameValuePairs, HTTP.UTF_8)); monitor.worked(1); DefaultHttpClient httpclient = Utils.getAPIClient(uri); String bodyResponse = retrieveWithLogin(httpclient, post, null, false, new SubProgressMonitor(monitor, 5)); JenkinsJobsResponse views = null; try { views = g.fromJson(bodyResponse, JenkinsJobsResponse.class); } catch (Exception e) { String body = bodyResponse; if (body != null && body.length() > 1000) { body = body.substring(0, 1000); } throw new CloudBeesException("Illegal JSON response for '" + uri + "': " + body, e); } if (views != null) { views.viewUrl = viewUrl; } if (views.jobs == null && views.primaryView != null && views.primaryView.jobs != null) { views.jobs = views.primaryView.jobs; } monitor.worked(4); return views; } catch (Exception e) { throw new CloudBeesException("Failed to get Jenkins jobs for '" + this.jenkins.url + "'.", e); } finally { monitor.done(); } } private void assertCorrectUrl(String viewUrl) throws CloudBeesException { if (viewUrl != null && !viewUrl.startsWith(this.jenkins.url)) { if (viewUrl != null && this.jenkins.alternativeUrl != null && !viewUrl.startsWith(this.jenkins.alternativeUrl)) { throw new CloudBeesException( "Unexpected url provided! Service url: " + this.jenkins.url + "; view url: " + viewUrl); } } } public JenkinsInstanceResponse getInstance(final IProgressMonitor monitor) throws CloudBeesException { StringBuffer errMsg = new StringBuffer(); String reqUrl = this.jenkins.url; if (!reqUrl.endsWith("/")) { reqUrl += "/"; } reqUrl += "api/json"; try { Gson g = Utils.createGson(); HttpPost post = new HttpPost(reqUrl); //post.setHeader("Accept", "application/json"); post.setHeader("Content-type", "application/x-www-form-urlencoded"); List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2); nameValuePairs.add(new BasicNameValuePair("tree", JenkinsInstanceResponse.QTREE)); post.setEntity(new UrlEncodedFormEntity(nameValuePairs, HTTP.UTF_8)); DefaultHttpClient httpclient = Utils.getAPIClient(reqUrl); String bodyResponse = retrieveWithLogin(httpclient, post, null, false, new SubProgressMonitor(monitor, 10)); if (bodyResponse == null) { throw new CloudBeesException("Failed to receive response from server"); } JenkinsInstanceResponse response = g.fromJson(bodyResponse, JenkinsInstanceResponse.class); if (response != null) { response.viewUrl = this.jenkins.url; response.atCloud = this.jenkins.atCloud; jenkins.alternativeUrl = response.primaryView.url; // Initializing jenkins instance alternativeUrl for lookups. Not perfectly nice solution in terms of reverse logic but looks like most feasible atm. if (response.views != null) { for (int i = 0; i < response.views.length; i++) { response.views[i].response = response; response.views[i].isPrimary = response.primaryView.url.equals(response.views[i].url); } } } return response; } catch (OperationCanceledException e) { throw e; } catch (Exception e) { throw new CloudBeesException("Failed to get Jenkins views for '" + reqUrl + "'. " + (errMsg.length() > 0 ? " (" + errMsg + ")" : ""), e); } } synchronized private String retrieveWithLogin(final DefaultHttpClient httpclient, final HttpRequestBase post, final List<NameValuePair> params, final boolean expectRedirect, final SubProgressMonitor monitor) throws UnsupportedEncodingException, IOException, ClientProtocolException, CloudBeesException, Exception { return (String) retrieveWithLogin(httpclient, post, params, expectRedirect, monitor, ResponseType.STRING); } synchronized private Object retrieveWithLogin(final DefaultHttpClient httpclient, final HttpRequestBase post, final List<NameValuePair> params, final boolean expectRedirect, final SubProgressMonitor monitor, final ResponseType responseType) throws UnsupportedEncodingException, IOException, ClientProtocolException, CloudBeesException, Exception { Object bodyResponse = null; if (this.jenkins.username != null && this.jenkins.username.trim().length() > 0 && this.jenkins.password != null && this.jenkins.password.trim().length() > 0) { post.addHeader("Authorization", "Basic " + Utils.toB64(this.jenkins.username + ":" + this.jenkins.password)); } List<NameValuePair> nvps = new ArrayList<NameValuePair>(); if (params != null) { nvps.addAll(params); } if (post instanceof HttpEntityEnclosingRequest) { if (((HttpEntityEnclosingRequest) post).getEntity() == null) { ((HttpEntityEnclosingRequest) post).setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8)); } } //CloudBeesCorePlugin.getDefault().getLogger().info("Jenkins request: " + post.getURI()); if (post instanceof HttpPost) { HttpPost pp = (HttpPost) post; String s; try { s = new Scanner(pp.getEntity().getContent()).useDelimiter("\\A").next(); } catch (java.util.NoSuchElementException e) { s = ""; } //CloudBeesCorePlugin.getDefault().getLogger().info("Jenkins request post params: " + s); } HttpResponse resp = httpclient.execute(post); switch (responseType) { case STRING: bodyResponse = Utils.getResponseBody(resp); break; case STREAM: bodyResponse = resp.getEntity().getContent(); break; case HTTP: bodyResponse = resp; break; } Utils.checkResponseCode(resp, expectRedirect, jenkins.atCloud); return bodyResponse; } public String getLabel() { return this.jenkins.label; } public String getUrl() { return this.jenkins.url; } public boolean isCloud() { return this.jenkins.atCloud; } /** * Returns url that was assigned by the JSON request by jenkins * * @return */ public String getAlternativeUrl() { return this.jenkins.alternativeUrl; } @Override public String toString() { if (this.jenkins != null) { return "JenkinsService[jenkinsInstance=" + this.jenkins + "]"; } return super.toString(); } public JenkinsBuildDetailsResponse getJobDetails(final String jobUrl, final IProgressMonitor monitor) throws CloudBeesException { monitor.setTaskName("Fetching Job details..."); assertCorrectUrl(jobUrl); StringBuffer errMsg = new StringBuffer(); String reqUrl = jobUrl; if (!reqUrl.endsWith("/")) { reqUrl = reqUrl + "/"; } String reqStr = reqUrl + "api/json"; try { Gson g = Utils.createGson(); HttpPost post = new HttpPost(reqStr); //post.setHeader("Accept", "application/json"); post.setHeader("Content-type", "application/x-www-form-urlencoded"); List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2); nameValuePairs.add(new BasicNameValuePair("tree", JenkinsBuildDetailsResponse.QTREE)); post.setEntity(new UrlEncodedFormEntity(nameValuePairs, HTTP.UTF_8)); DefaultHttpClient httpclient = Utils.getAPIClient(reqStr); String bodyResponse = retrieveWithLogin(httpclient, post, null, false, new SubProgressMonitor(monitor, 10)); JenkinsBuildDetailsResponse details = g.fromJson(bodyResponse, JenkinsBuildDetailsResponse.class); if (details.getDisplayName() == null) { throw new CloudBeesException("Response does not contain required fields!"); } if (details.viewUrl == null) { details.viewUrl = jobUrl; } return details; } catch (Exception e) { throw new CloudBeesException("Failed to get Jenkins jobs for '" + jobUrl + "'. " + (errMsg.length() > 0 && errMsg.length() < 1000 ? " (" + errMsg + ")" : "") + "Request string:" + reqStr, e); } } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (this.jenkins == null ? 0 : this.jenkins.hashCode()); return result; } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } JenkinsService other = (JenkinsService) obj; if (this.jenkins == null) { if (other.jenkins != null) { return false; } } else if (!this.jenkins.equals(other.jenkins)) { return false; } return true; } public JenkinsJobAndBuildsResponse getJobBuilds(final String jobUrl, final IProgressMonitor monitor) throws CloudBeesException { monitor.setTaskName("Fetching Job builds..."); assertCorrectUrl(jobUrl); StringBuffer errMsg = new StringBuffer(); String reqUrl = jobUrl; if (!reqUrl.endsWith("/")) { reqUrl = reqUrl + "/"; } String reqStr = reqUrl + "api/json"; try { Gson g = Utils.createGson(); HttpPost post = new HttpPost(reqStr); //post.setHeader("Accept", "application/json"); post.setHeader("Content-type", "application/x-www-form-urlencoded"); List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2); nameValuePairs.add(new BasicNameValuePair("tree", JenkinsJobAndBuildsResponse.QTREE)); post.setEntity(new UrlEncodedFormEntity(nameValuePairs, HTTP.UTF_8)); DefaultHttpClient httpclient = Utils.getAPIClient(reqStr); String bodyResponse = retrieveWithLogin(httpclient, post, null, false, new SubProgressMonitor(monitor, 10)); JenkinsJobAndBuildsResponse details = g.fromJson(bodyResponse, JenkinsJobAndBuildsResponse.class); if (details.name == null) { throw new CloudBeesException("Response does not contain required fields!"); } details.viewUrl = jobUrl; // this.jenkins.url; return details; } catch (Exception e) { throw new CloudBeesException("Failed to get Jenkins jobs for '" + jobUrl + "'. " + (errMsg.length() > 0 && errMsg.length() < 1000 ? " (" + errMsg + ")" : "") + "Request string:" + reqStr, e); } } private JenkinsScmConfig getJobScmConfig(final String jobUrl, final IProgressMonitor monitor) throws CloudBeesException { monitor.setTaskName("Fetching Job SCM config..."); assertCorrectUrl(jobUrl); StringBuffer errMsg = new StringBuffer(); String reqUrl = jobUrl; if (!reqUrl.endsWith("/")) { reqUrl = reqUrl + "/"; } String reqStr = reqUrl + "config.xml"; String bodyResponse = null; try { DefaultHttpClient httpclient = Utils.getAPIClient(reqStr); HttpGet post = new HttpGet(reqStr); post.setHeader("Accept", "text/html,application/xhtml+xml,application/xml"); bodyResponse = retrieveWithLogin(httpclient, post, null, false, new SubProgressMonitor(monitor, 10)); JenkinsScmConfig config = JenkinsConfigParser.parse(bodyResponse); return config; } catch (Exception e) { throw new CloudBeesException("Failed to get Jenkins SCM config from '" + reqStr + "'. " + (errMsg.length() > 0 ? " (" + errMsg + ")" : "") + (bodyResponse == null ? "" : " - Response: " + bodyResponse), e); } } public void invokeBuild(final String jobUrl, final Map<String, String> props, final IProgressMonitor monitor) throws CloudBeesException { monitor.setTaskName("Invoking build request..."); assertCorrectUrl(jobUrl); StringBuffer errMsg = new StringBuffer(); String reqUrl = jobUrl; if (!reqUrl.endsWith("/")) { reqUrl = reqUrl + "/"; } String reqStr; if (props == null || props.isEmpty()) { reqStr = reqUrl + "build"; } else { reqStr = reqUrl + "buildWithParameters"; } List<NameValuePair> params = null; if (props != null) { for (Map.Entry<String, String> entry : props.entrySet()) { if (params == null) { params = new ArrayList<NameValuePair>(); } params.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); } } try { DefaultHttpClient httpclient = Utils.getAPIClient(reqStr); HttpPost post = new HttpPost(reqStr); retrieveWithLogin(httpclient, post, params, true, new SubProgressMonitor(monitor, 10)); } catch (Exception e) { throw new CloudBeesException("Failed to get invoke JenkinsBuild for '" + jobUrl + "'. " + (errMsg.length() > 0 ? " (" + errMsg + ")" : "") + "Request string:" + reqStr, e); } } public JenkinsScmConfig getJenkinsScmConfig(final String jobUrl, final IProgressMonitor monitor) throws CloudBeesException { // TODO invalidate old items JenkinsScmConfig scm = this.scms.get(jobUrl); if (scm == null) { scm = getJobScmConfig(jobUrl, monitor); if (scm != null) { this.scms.put(jobUrl, scm); } } return scm; } public InputStream getTestReport(final String url, final IProgressMonitor monitor) throws CloudBeesException { monitor.setTaskName("Fetching Jenkins build Test Report..."); assertCorrectUrl(url); StringBuffer errMsg = new StringBuffer(); String reqUrl = url; if (!reqUrl.endsWith("/")) { reqUrl = reqUrl + "/"; } String reqStr = reqUrl + "testReport/api/xml"; InputStream bodyResponse = null; try { DefaultHttpClient httpclient = Utils.getAPIClient(reqStr); HttpGet post = new HttpGet(reqStr); post.setHeader("Accept", "text/html,application/xhtml+xml,application/xml"); bodyResponse = (InputStream) retrieveWithLogin(httpclient, post, null, false, new SubProgressMonitor(monitor, 10), ResponseType.STREAM); return bodyResponse; } catch (Exception e) { throw new CloudBeesException("Failed to get Jenkins job test report for '" + url + "'. " + (errMsg.length() > 0 ? " (" + errMsg + ")" : "") + "Request string: " + reqStr + " - Response: " + Utils.readString(bodyResponse), e); } } public void createJenkinsJob(final String jobName, final String configXML, final IProgressMonitor monitor) throws CloudBeesException { try { monitor.setTaskName("Preparing new job request"); String encodedJobName = URLEncoder.encode(jobName, "UTF-8"); String url = this.jenkins.url.endsWith("/") ? this.jenkins.url : this.jenkins.url + "/"; String reqUrl = url + "createItem?name=" + encodedJobName; HttpPost post = new HttpPost(reqUrl); StringEntity strEntity = new StringEntity(configXML, "application/xml", "UTF-8"); post.setEntity(strEntity); DefaultHttpClient httpClient = Utils.getAPIClient(reqUrl); monitor.setTaskName("Creating new Jenkins job..."); retrieveWithLogin(httpClient, post, null, false, new SubProgressMonitor(monitor, 10)); } catch (Exception e) { throw new CloudBeesException("Failed to create new Jenkins job", e); } } public void deleteJenkinsJob(final String joburl, final IProgressMonitor monitor) throws CloudBeesException { try { monitor.setTaskName("Preparing delete request"); String url = joburl.endsWith("/") ? joburl : joburl + "/"; String reqUrl = url + "doDelete"; HttpPost post = new HttpPost(reqUrl); post.setEntity(new StringEntity("")); DefaultHttpClient httpClient = Utils.getAPIClient(reqUrl); monitor.setTaskName("Deleting Jenkins job..."); retrieveWithLogin(httpClient, post, null, true, new SubProgressMonitor(monitor, 10)); } catch (Exception e) { throw new CloudBeesException("Failed to delete Jenkins job", e); } } public JenkinsConsoleLogResponse getBuildLog(final JenkinsConsoleLogResponse request, final IProgressMonitor monitor) throws CloudBeesException { monitor.setTaskName("Fetching Job build log..."); String url = request.viewUrl; assertCorrectUrl(url); StringBuffer errMsg = new StringBuffer(); String reqUrl = url; if (!reqUrl.endsWith("/")) { reqUrl = reqUrl + "/"; } String reqStr = reqUrl + "logText/progressiveText?start=" + request.start; try { DefaultHttpClient httpclient = Utils.getAPIClient(reqStr); HttpPost post = new HttpPost(reqStr); if (request.annotator != null) { post.setHeader("X-ConsoleAnnotator", request.annotator); } HttpResponse response = (HttpResponse) retrieveWithLogin(httpclient, post, null, false, new SubProgressMonitor(monitor, 10), ResponseType.HTTP); // for (Header head : response.getAllHeaders()) { // System.out.println("header: " + head); // } request.logPart = response.getEntity().getContent(); try { Header moreData = response.getLastHeader("X-More-Data"); request.hasMore = moreData != null ? "true".equalsIgnoreCase(moreData.getValue()) : false; Header textSize = response.getLastHeader("X-Text-Size"); request.start = textSize != null ? Long.parseLong(textSize.getValue()) : Long.MAX_VALUE; Header annotator = response.getLastHeader("X-ConsoleAnnotator"); if (annotator != null) { request.annotator = annotator.getValue(); } } catch (Exception e) { e.printStackTrace(); request.hasMore = false; } return request; } catch (Exception e) { throw new CloudBeesException("Failed to get Jenkins build log for '" + url + "'. " + (errMsg.length() > 0 && errMsg.length() < 1000 ? " (" + errMsg + ")" : "") + "Request string:" + reqStr, e); } } public InputStream getArtifact(final String url, final IProgressMonitor monitor) throws CloudBeesException { monitor.setTaskName("Fetching Jenkins artifact..."); assertCorrectUrl(url); StringBuffer errMsg = new StringBuffer(); InputStream bodyResponse = null; try { DefaultHttpClient httpclient = Utils.getAPIClient(url); HttpGet post = new HttpGet(url); bodyResponse = (InputStream) retrieveWithLogin(httpclient, post, null, false, new SubProgressMonitor(monitor, 10), ResponseType.STREAM); return bodyResponse; } catch (Exception e) { String readString = Utils.readString(bodyResponse); if (readString != null && readString.length() > 100) { readString = readString.substring(0, 100); } throw new CloudBeesException("Failed to get Jenkins artifact '" + url + "'. " + (errMsg.length() > 0 ? " (" + errMsg + ")" : "") + " - Response: " + readString, e); } } }