Java tutorial
// Copyright (C) 2013 GerritForge www.gerritforge.com // // 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 mobi.jenkinsci.ci; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; import mobi.jenkinsci.ci.client.ArtifactFingerprint; import mobi.jenkinsci.ci.client.JenkinsClient; import mobi.jenkinsci.ci.model.Build; import mobi.jenkinsci.ci.model.ChangeSetItem; import mobi.jenkinsci.ci.model.ComputerList; import mobi.jenkinsci.ci.model.FailedTestsList; import mobi.jenkinsci.ci.model.JenkinsItem; import mobi.jenkinsci.ci.model.Job; import mobi.jenkinsci.ci.model.Queue; import mobi.jenkinsci.ci.model.ViewList; import mobi.jenkinsci.commons.Account; import mobi.jenkinsci.exceptions.TwoPhaseAuthenticationRequiredException; import mobi.jenkinsci.model.AbstractNode; import mobi.jenkinsci.model.HeaderNode; import mobi.jenkinsci.model.ItemNode; import mobi.jenkinsci.model.Layout; import mobi.jenkinsci.model.RawBinaryNode; import mobi.jenkinsci.net.UrlPath; import mobi.jenkinsci.plugin.Plugin; import mobi.jenkinsci.plugin.PluginConfig; import mobi.jenkinsci.plugin.URLDownloader; import org.apache.commons.io.IOUtils; import org.apache.http.Header; import org.apache.http.HeaderElement; import org.apache.http.HttpHeaders; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; 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.ByteArrayEntity; public class JenkinsCIPlugin implements Plugin, URLDownloader { private static final Pattern JOB_PATTERN = Pattern.compile("/job/([^/]+)"); private static final Pattern JOB_BUILD_PATTERN = Pattern.compile("/job/([^/]+)/([^/]+)"); private static final Pattern APK_MD5_PATTERN = Pattern.compile("APK-MD5/([0-9a-f]+)"); private static final List<String> HEADERS_BLACKLIST = Arrays.asList(new String[] { HttpHeaders.CONTENT_LENGTH, HttpHeaders.HOST, HttpHeaders.AUTHORIZATION, HttpHeaders.CONNECTION, "Cookie", "Origin", HttpHeaders.REFERER, HttpHeaders.ACCEPT_ENCODING, HttpHeaders.CONTENT_ENCODING }); @Override public AbstractNode processRequest(final Account account, final HttpServletRequest req, final PluginConfig pluginConf) throws IOException { final JenkinsClient client = JenkinsClient.getInstance(account, pluginConf); final UrlPath reqPath = new UrlPath(req.getRequestURI()); final String command = req.getParameter("cmd"); if (command != null) { return executeCommand(command, pluginConf, client, reqPath); } else { return getNode(account, pluginConf, reqPath); } } private AbstractNode executeCommand(final String command, final PluginConfig pluginConf, final JenkinsClient client, final UrlPath reqPath) throws IOException { if (command.startsWith("load:/")) { final String url = command.substring("load:/".length()); if (url.endsWith("testReport")) { final FailedTestsList list = client.getFailedTestsList(); return list.toAbstractNode(pluginConf.getUrl()); } else { return null; } } else { return client.execute(reqPath, command); } } @Override public String validateConfig(final HttpServletRequest req, final Account account, final PluginConfig pluginConf) throws TwoPhaseAuthenticationRequiredException { try { final JenkinsClient client = JenkinsClient.getInstance(account, pluginConf); final ViewList views = client.getViewList(req); if (views == null || views.getViews() == null || views.getViews().size() <= 0) { return "Unable to retrieve views from Jenkins at " + pluginConf.getUrl(); } } catch (final MalformedURLException e) { return "Invalid Jenkins Server URL:\n" + pluginConf.getUrl(); } catch (final TwoPhaseAuthenticationRequiredException e) { throw e; } catch (final IOException e) { final String localizedMessage = e.getLocalizedMessage(); return "Error contacting Jenkins at " + pluginConf.getUrl() + (localizedMessage != null ? "\n" + localizedMessage : ""); } return null; } @Override public RawBinaryNode download(final HttpServletRequest req, final String url, final Account account, final PluginConfig pluginConf) throws IOException { final JenkinsClient client = JenkinsClient.getInstance(account, pluginConf); final HttpRequestBase get = getNewHttpRequest(req, url); final ByteArrayOutputStream out = new ByteArrayOutputStream(); final RawBinaryNode result = new RawBinaryNode(); try { final HttpResponse response = client.http.execute(get); final StatusLine status = response.getStatusLine(); if (status.getStatusCode() != HttpURLConnection.HTTP_OK) { throw new IOException("HTTP-" + get.getMethod() + " " + url + " failed: status code " + status); } IOUtils.copy(response.getEntity().getContent(), out); for (final Header h : response.getAllHeaders()) { final String headerName = h.getName(); if (headerName.equalsIgnoreCase(HttpHeaders.CONTENT_TYPE)) { result.setHttpContentType(h.getValue()); } else if (headerName.equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH)) { result.setSize(Long.parseLong(h.getValue())); } else if (headerName.equalsIgnoreCase(HttpHeaders.CONTENT_ENCODING)) { result.setHttpCharacterEncoding(h.getValue()); } } } finally { get.releaseConnection(); } result.setData(new ByteArrayInputStream(out.toByteArray())); return result; } private HttpRequestBase getNewHttpRequest(final HttpServletRequest req, final String url) throws IOException { HttpRequestBase newReq = null; if (req == null || req.getMethod().equalsIgnoreCase("get")) { newReq = new HttpGet(url); } else { final HttpPost post = new HttpPost(url); final ByteArrayOutputStream reqBody = new ByteArrayOutputStream(); copyHeaders(post, req); IOUtils.copy(req.getInputStream(), reqBody); post.setEntity(new ByteArrayEntity(reqBody.toByteArray())); newReq = post; } return newReq; } private void copyHeaders(final HttpPost post, final HttpServletRequest req) { for (final Enumeration<String> headerNames = req.getHeaderNames(); headerNames.hasMoreElements();) { final String headerName = headerNames.nextElement(); if (HEADERS_BLACKLIST.contains(headerName)) { continue; } post.setHeader(headerName, req.getHeader(headerName)); } } protected byte[] retrieveUrl(final String userAgent, final String linkUrl, final HashMap<String, HeaderElement[]> contentHeaders, final Object ctx) throws Exception { final JenkinsClient client = (JenkinsClient) ctx; final HttpRequestBase get = getNewHttpRequest(null, linkUrl); if (userAgent != null) { get.setHeader("User-Agent", userAgent); } final HttpResponse response = client.http.execute(get); try { final int status = response.getStatusLine().getStatusCode(); if (status != HttpURLConnection.HTTP_OK) { throw new IOException("HTTP- " + get.getMethod() + " " + linkUrl + " returned status " + status); } if (contentHeaders != null) { for (final Header header : response.getAllHeaders()) { contentHeaders.put(header.getName(), header.getElements()); } } final ByteArrayOutputStream out = new ByteArrayOutputStream(); final InputStream content = response.getEntity().getContent(); IOUtils.copy(content, out); content.close(); return out.toByteArray(); } finally { get.releaseConnection(); } } public ItemNode getNode(final Account account, final PluginConfig pluginConf, final UrlPath pathHelper) throws IOException { final JenkinsClient client = JenkinsClient.getInstance(account, pluginConf); final String urlPrefix = pluginConf.getUrl(); final ItemNode rootNode = new ItemNode(); JenkinsItem viewList = null; if (pathHelper.isRootPath() || pathHelper.isFollowingPath("views")) { viewList = getViewJobNode(pathHelper, client); if (!pathHelper.isRootPath()) { return viewList.toAbstractNode(urlPrefix); } } ComputerList computerList = null; if (pathHelper.isRootPath() || pathHelper.isFollowingPath("computer")) { computerList = client.getComputerList(); } Queue queue = null; if (pathHelper.isRootPath() || pathHelper.isFollowingPath("queue")) { queue = client.getQueue(); } if (pathHelper.isRootPath()) { rootNode.setLayout(Layout.ICONS); } rootNode.setPath("/"); rootNode.setVersion(ItemNode.API_VERSION); if (viewList != null) { final ItemNode node = viewList.toAbstractNode(urlPrefix); rootNode.addNode(node); } if (computerList != null) { final ItemNode node = computerList.toAbstractNode(urlPrefix); rootNode.addNode(node); } if (queue != null) { final ItemNode node = queue.toAbstractNode(urlPrefix); rootNode.addNode(node); } return rootNode; } public JenkinsItem getViewJobNode(final UrlPath pathHelper, final JenkinsClient client) throws IOException { final String viewPathComponents = pathHelper.getPathSuffix("views"); return client.getViewList().getSubItem(this, viewPathComponents); } @Override public void init() { } @Override public String getType() { return "JenkinsCI"; } @Override public List<ItemNode> getEntryPoints(final PluginConfig pluginConf) throws IOException { final List<ItemNode> result = new LinkedList<ItemNode>(); ItemNode entryPoint = new ItemNode(); entryPoint.setTitle("Builds"); entryPoint.setPath("views"); entryPoint.setIcon("?image=icons/views_wall.png"); result.add(entryPoint); entryPoint = new ItemNode(); entryPoint.setTitle("Nodes"); entryPoint.setPath("computer"); entryPoint.setIcon("?image=icons/nodes_wall.png"); result.add(entryPoint); entryPoint = new ItemNode(); entryPoint.setTitle("Queue"); entryPoint.setPath("queue"); entryPoint.setIcon("?image=icons/queue_wall.png"); result.add(entryPoint); return result; } @Override public List<ItemNode> getReleaseNotes(final Account account, final PluginConfig pluginConf, final String version, final String url, final HttpServletRequest request) throws Exception { final ArrayList<ItemNode> changes = new ArrayList<ItemNode>(); final JenkinsClient client = JenkinsClient.getInstance(account, pluginConf); final String jenkinsUrl = pluginConf.getUrl(); final String jobPathPart = getJobPathPart(url, jenkinsUrl); final Job job = client.getJob(jobPathPart); final String jobBuildPart = getJobBuildPart(client, url, jenkinsUrl); final int jobBuildNumber = job.getBuild(jobBuildPart).number; final HashMap<URL, ItemNode> ticketsMap = new HashMap<URL, ItemNode>(); final List<ItemNode> changesList = new ArrayList<ItemNode>(); final ArtifactFingerprint remoteBuildFingerprint = client.getArtifactFromMD5(getApkMd5(request)); if (job.builds.size() <= 0 || remoteBuildFingerprint.original.number >= job.builds.get(0).number) { throw new Exception("Nothing to upgrade: you already have the latest build"); } for (final Build build : job.builds) { if (build.number > remoteBuildFingerprint.original.number && build.number <= jobBuildNumber) { for (final ChangeSetItem jobChange : client.getJobChanges(jobPathPart, build.number).items) { if (jobChange.issue != null) { final URL ticketUrl = jobChange.issue.linkUrl; if (ticketsMap.get(ticketUrl) == null) { ticketsMap.put(ticketUrl, account.getPluginNodeForUrl(pluginConf, ticketUrl)); } } else { changesList.add(jobChange.toAbstractNode(jenkinsUrl)); } } } } if (!ticketsMap.isEmpty()) { changes.add(new HeaderNode("New features / Fixed bugs")); changes.addAll(ticketsMap.values()); } if (!changesList.isEmpty()) { changes.add(new HeaderNode("Code changes")); changes.addAll(changesList); } return changes; } private String getApkMd5(final HttpServletRequest request) { final Matcher apkMd5Match = APK_MD5_PATTERN.matcher(request.getHeader(HttpHeaders.USER_AGENT)); if (apkMd5Match.find()) { return apkMd5Match.group(1); } else { return null; } } private String getJobBuildPart(final JenkinsClient client, final String jobUrlString, final String jenkinsUrlString) throws Exception { final String jobPath = getJobPath(jobUrlString, jenkinsUrlString); final Matcher jobMatch = JOB_BUILD_PATTERN.matcher(jobPath); if (!jobMatch.find()) { throw new MalformedURLException("Cannot find job name in path " + jobPath); } return jobMatch.group(2); } private boolean isSameHost(final URL jobUrl, final URL jenkinsUrl) { final String jobHost = jobUrl.getHost(); final String jenkinsHost = jenkinsUrl.getHost(); return jobHost.equalsIgnoreCase(jenkinsHost); } private String getJobPathPart(final String jobUrlString, final String jenkinsUrlString) throws MalformedURLException { final String jobPath = getJobPath(jobUrlString, jenkinsUrlString); final Matcher jobMatch = JOB_PATTERN.matcher(jobPath); if (!jobMatch.find()) { throw new MalformedURLException("Cannot find job name in path " + jobPath); } return jobMatch.group(1); } private String getJobPath(final String jobUrlString, final String jenkinsUrlString) throws MalformedURLException { String jobPath; final URL jobUrl = new URL(jobUrlString); final URL jenkinsUrl = new URL(jenkinsUrlString); if (!isSameHost(jobUrl, jenkinsUrl)) { throw new MalformedURLException( "URLs " + jobUrlString + " and Jenkins " + jenkinsUrlString + " have a different hostname"); } jobPath = jobUrl.getPath(); final String jenkinsPath = jenkinsUrl.getPath(); if (!jobPath.startsWith(jenkinsPath)) { throw new MalformedURLException("Job path " + jobPath + " is not within Jenkins path " + jenkinsPath); } return jobPath; } @Override public ItemNode claim(final Account account, final PluginConfig pluginConf, final URL url) { return null; } }