From 610ded57bf9d359b0e979d4c875f67dc6b8f54ef Mon Sep 17 00:00:00 2001 From: Dell Green Date: Thu, 5 Oct 2017 20:15:38 +0100 Subject: [PATCH] Refactor: #291, useful utility methods refactored into utility classes --- ReleaseNotes.md | 4 + .../com/offbytwo/jenkins/JenkinsServer.java | 108 +----- .../jenkins/client/JenkinsHttpClient.java | 90 ++--- .../jenkins/client/util/ResponseUtils.java | 38 ++ .../jenkins/client/util/UrlUtils.java | 156 +++++++++ .../jenkins/client/JenkinsHttpClientTest.java | 56 +++ .../client/util/ResponseUtilsTest.java | 49 +++ .../jenkins/client/util/UrlUtilsTest.java | 324 ++++++++++++++++++ 8 files changed, 677 insertions(+), 148 deletions(-) create mode 100644 jenkins-client/src/main/java/com/offbytwo/jenkins/client/util/ResponseUtils.java create mode 100644 jenkins-client/src/main/java/com/offbytwo/jenkins/client/util/UrlUtils.java create mode 100644 jenkins-client/src/test/java/com/offbytwo/jenkins/client/JenkinsHttpClientTest.java create mode 100644 jenkins-client/src/test/java/com/offbytwo/jenkins/client/util/ResponseUtilsTest.java create mode 100644 jenkins-client/src/test/java/com/offbytwo/jenkins/client/util/UrlUtilsTest.java diff --git a/ReleaseNotes.md b/ReleaseNotes.md index f0f7142b..c4a6f533 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -2,6 +2,10 @@ ## Release 0.3.8 (NOT RELEASED YET) + * [Refactor Issue 291][issue-291] + + Useful utility methods refactored into utility classes. + * [Fixed Issue 282][issue-282] `NullPointerException` may be thrown if `upstreamUrl` is `null` when diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/JenkinsServer.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/JenkinsServer.java index 7b8f77a8..0a6f79a4 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/JenkinsServer.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/JenkinsServer.java @@ -8,7 +8,6 @@ import java.io.IOException; import java.net.URI; -import java.util.Arrays; import java.util.List; import java.util.Map; @@ -27,6 +26,7 @@ import com.google.common.collect.Maps; import com.offbytwo.jenkins.client.JenkinsHttpClient; import com.offbytwo.jenkins.client.util.EncodingUtils; +import com.offbytwo.jenkins.client.util.UrlUtils; import com.offbytwo.jenkins.helper.JenkinsVersion; import com.offbytwo.jenkins.model.Build; import com.offbytwo.jenkins.model.Computer; @@ -155,7 +155,7 @@ public Map getJobs(String view) throws IOException { * @throws IOException in case of an error. */ public Map getJobs(FolderJob folder, String view) throws IOException { - String path = toBaseUrl(folder); + String path = UrlUtils.toBaseUrl(folder); Class viewClass = MainView.class; if (view != null) { path = path + "view/" + EncodingUtils.encode(view) + "/"; @@ -192,7 +192,7 @@ public Map getViews() throws IOException { public Map getViews(FolderJob folder) throws IOException { // This is much better than using &depth=2 // http://localhost:8080/api/json?pretty&tree=views[name,url,jobs[name,url]] - List views = client.get(toBaseUrl(folder) + "?tree=views[name,url,jobs[name,url]]", MainView.class).getViews(); + List views = client.get(UrlUtils.toBaseUrl(folder) + "?tree=views[name,url,jobs[name,url]]", MainView.class).getViews(); return Maps.uniqueIndex(views, new Function() { @Override public String apply(View view) { @@ -232,7 +232,7 @@ public View getView(String name) throws IOException { */ public View getView(FolderJob folder, String name) throws IOException { try { - View resultView = client.get(toViewBaseUrl(folder, name) + "/", View.class); + View resultView = client.get(UrlUtils.toViewBaseUrl(folder, name) + "/", View.class); resultView.setClient(client); // TODO: Think about the following? Does there exists a simpler/more @@ -262,7 +262,7 @@ public View getView(FolderJob folder, String name) throws IOException { * @throws IOException in case of an error. */ public JobWithDetails getJob(String jobName) throws IOException { - return getJob(null, parseFullName(jobName)); + return getJob(null, UrlUtils.toFullJobPath(jobName)); } /** @@ -275,7 +275,7 @@ public JobWithDetails getJob(String jobName) throws IOException { */ public JobWithDetails getJob(FolderJob folder, String jobName) throws IOException { try { - JobWithDetails job = client.get(toJobBaseUrl(folder, jobName), JobWithDetails.class); + JobWithDetails job = client.get(UrlUtils.toJobBaseUrl(folder, jobName), JobWithDetails.class); job.setClient(client); return job; @@ -290,12 +290,12 @@ public JobWithDetails getJob(FolderJob folder, String jobName) throws IOExceptio } public MavenJobWithDetails getMavenJob(String jobName) throws IOException { - return getMavenJob(null, parseFullName(jobName)); + return getMavenJob(null, UrlUtils.toFullJobPath(jobName)); } public MavenJobWithDetails getMavenJob(FolderJob folder, String jobName) throws IOException { try { - MavenJobWithDetails job = client.get(toJobBaseUrl(folder, jobName), MavenJobWithDetails.class); + MavenJobWithDetails job = client.get(UrlUtils.toJobBaseUrl(folder, jobName), MavenJobWithDetails.class); job.setClient(client); return job; @@ -381,7 +381,7 @@ public void createJob(FolderJob folder, String jobName, String jobXml) throws IO * @throws IOException in case of an error. */ public void createJob(FolderJob folder, String jobName, String jobXml, Boolean crumbFlag) throws IOException { - client.post_xml(toBaseUrl(folder) + "createItem?name=" + EncodingUtils.encodeParam(jobName), jobXml, crumbFlag); + client.post_xml(UrlUtils.toBaseUrl(folder) + "createItem?name=" + EncodingUtils.encodeParam(jobName), jobXml, crumbFlag); } /** @@ -433,7 +433,7 @@ public void createView(FolderJob folder, String viewName, String viewXml) throws * @throws IOException in case of an error. */ public void createView(FolderJob folder, String viewName, String viewXml, Boolean crumbFlag) throws IOException { - client.post_xml(toBaseUrl(folder) + "createView?name=" + EncodingUtils.encodeParam(viewName), viewXml, + client.post_xml(UrlUtils.toBaseUrl(folder) + "createView?name=" + EncodingUtils.encodeParam(viewName), viewXml, crumbFlag); } @@ -484,7 +484,7 @@ public void createFolder(FolderJob folder, String jobName, Boolean crumbFlag) th // here ImmutableMap params = ImmutableMap.of("mode", "com.cloudbees.hudson.plugins.folder.Folder", "name", EncodingUtils.encodeParam(jobName), "from", "", "Submit", "OK"); - client.post_form(toBaseUrl(folder) + "createItem?", params, crumbFlag); + client.post_form(UrlUtils.toBaseUrl(folder) + "createItem?", params, crumbFlag); } /** @@ -507,7 +507,7 @@ public String getJobXml(String jobName) throws IOException { * @throws IOException in case of an error. */ public String getJobXml(FolderJob folder, String jobName) throws IOException { - return client.get(toJobBaseUrl(folder, jobName) + "/config.xml"); + return client.get(UrlUtils.toJobBaseUrl(folder, jobName) + "/config.xml"); } /** @@ -577,11 +577,11 @@ public void updateView(String viewName, String viewXml, boolean crumbFlag) throw } public void updateView(FolderJob folder, String viewName, String viewXml) throws IOException { - client.post_xml(toBaseUrl(folder) + "view/" + EncodingUtils.encode(viewName) + "/config.xml", viewXml, true); + client.post_xml(UrlUtils.toBaseUrl(folder) + "view/" + EncodingUtils.encode(viewName) + "/config.xml", viewXml, true); } public void updateView(FolderJob folder, String viewName, String viewXml, boolean crumbFlag) throws IOException { - client.post_xml(toBaseUrl(folder) + "view/" + EncodingUtils.encode(viewName) + "/config.xml", viewXml, crumbFlag); + client.post_xml(UrlUtils.toBaseUrl(folder) + "view/" + EncodingUtils.encode(viewName) + "/config.xml", viewXml, crumbFlag); } /** @@ -617,7 +617,7 @@ public void updateJob(String jobName, String jobXml, boolean crumbFlag) throws I * @throws IOException in case of an error. */ public void updateJob(FolderJob folder, String jobName, String jobXml, boolean crumbFlag) throws IOException { - client.post_xml(toJobBaseUrl(folder, jobName) + "/config.xml", jobXml, crumbFlag); + client.post_xml(UrlUtils.toJobBaseUrl(folder, jobName) + "/config.xml", jobXml, crumbFlag); } /** @@ -685,7 +685,7 @@ public void deleteJob(FolderJob folder, String jobName) throws IOException { * @throws IOException in case of problems. */ public void deleteJob(FolderJob folder, String jobName, boolean crumbFlag) throws IOException { - client.post(toJobBaseUrl(folder, jobName) + "/doDelete", crumbFlag); + client.post(UrlUtils.toJobBaseUrl(folder, jobName) + "/doDelete", crumbFlag); } /** @@ -878,80 +878,10 @@ public void renameJob(FolderJob folder, String oldJobName, String newJobName) th */ public void renameJob(FolderJob folder, String oldJobName, String newJobName, Boolean crumbFlag) throws IOException { - client.post(toJobBaseUrl(folder, oldJobName) + "/doRename?newName=" + EncodingUtils.encodeParam(newJobName), - crumbFlag); - } - - /** - * Helper to create a base url in case a folder is given - * - * @param folder the folder or {@code null} - * @return The created base url. - */ - private String toBaseUrl(FolderJob folder) { - String path = "/"; - if (folder != null) { - path = folder.getUrl(); - } - return path; - } - - /** - * Helper to create the base url for a job, with or without a given folder - * - * @param folder the folder or {@code null} - * @param jobName the name of the job. - * @return converted base url. - */ - private String toJobBaseUrl(FolderJob folder, String jobName) { - String jobBaseUrl = toBaseUrl(folder) + "job/"; - - String[] jobNameParts = jobName.split("/"); - for (int i = 0; i < jobNameParts.length; i++) { - jobBaseUrl += EncodingUtils.encode(jobNameParts[i]); - - if (i != jobNameParts.length - 1) { - jobBaseUrl += "/"; - } - } - - return jobBaseUrl; + client.post(UrlUtils.toJobBaseUrl(folder, oldJobName) + + "/doRename?newName=" + EncodingUtils.encodeParam(newJobName), + crumbFlag); } - /** - * Helper to create the base url for a view, with or without a given folder - * - * @param folder the folder or {@code null} - * @param name the of the view. - * @return converted view url. - */ - private String toViewBaseUrl(FolderJob folder, String name) { - return toBaseUrl(folder) + "view/" + EncodingUtils.encode(name); - } - /** - * Parses the provided job name for folders to get the full path for the job. - * @param jobName the fullName of the job. - * @return the path of the job including folders if present. - */ - private String parseFullName(String jobName) - { - if (!jobName.contains("/")) { - return jobName; - } - - List foldersAndJob = Arrays.asList(jobName.split("/")); - - String foldersAndJobName = ""; - - for (int i = 0; i < foldersAndJob.size(); i++) { - foldersAndJobName += foldersAndJob.get(i); - - if (i != foldersAndJob.size() -1) { - foldersAndJobName += "/job/"; - } - } - - return foldersAndJobName; - } } diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/client/JenkinsHttpClient.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/client/JenkinsHttpClient.java index 8c5c467a..e527e0b2 100755 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/client/JenkinsHttpClient.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/client/JenkinsHttpClient.java @@ -18,7 +18,6 @@ import net.sf.json.JSONObject; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; -import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.auth.AuthScope; @@ -48,6 +47,8 @@ import java.util.Map; import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; +import com.offbytwo.jenkins.client.util.ResponseUtils; +import com.offbytwo.jenkins.client.util.UrlUtils; import static org.apache.commons.lang.StringUtils.isNotBlank; //import com.offbytwo.jenkins.client.util.HttpResponseContentExtractor; @@ -132,10 +133,10 @@ public JenkinsHttpClient(URI uri, String username, String password) { * @throws IOException in case of an error. */ public T get(String path, Class cls) throws IOException { - HttpGet getMethod = new HttpGet(api(path)); + HttpGet getMethod = new HttpGet(UrlUtils.toJsonApiUri(uri, context, path)); HttpResponse response = client.execute(getMethod, localContext); - getJenkinsVersionFromHeader(response); + jenkinsVersion = ResponseUtils.getJenkinsVersion(response); try { httpResponseValidator.validateResponse(response); return objectFromResponse(cls, response); @@ -154,9 +155,9 @@ public T get(String path, Class cls) throws IOException * @throws IOException in case of an error. */ public String get(String path) throws IOException { - HttpGet getMethod = new HttpGet(api(path)); + HttpGet getMethod = new HttpGet(UrlUtils.toJsonApiUri(uri, context, path)); HttpResponse response = client.execute(getMethod, localContext); - getJenkinsVersionFromHeader(response); + jenkinsVersion = ResponseUtils.getJenkinsVersion(response); LOGGER.debug("get({}), version={}, responseCode={}", path, this.jenkinsVersion, response.getStatusLine().getStatusCode()); try { @@ -200,7 +201,7 @@ public T getQuietly(String path, Class cls) { public InputStream getFile(URI path) throws IOException { HttpGet getMethod = new HttpGet(path); HttpResponse response = client.execute(getMethod, localContext); - getJenkinsVersionFromHeader(response); + jenkinsVersion = ResponseUtils.getJenkinsVersion(response); httpResponseValidator.validateResponse(response); return new RequestReleasingInputStream(response.getEntity().getContent(), getMethod); } @@ -222,7 +223,7 @@ public R post(String path, D data, Class cls) throws * @throws IOException in case of an error. */ public R post(String path, D data, Class cls, boolean crumbFlag) throws IOException { - HttpPost request = new HttpPost(api(path)); + HttpPost request = new HttpPost(UrlUtils.toJsonApiUri(uri, context, path)); if (crumbFlag == true) { Crumb crumb = getQuietly("/crumbIssuer", Crumb.class); if (crumb != null) { @@ -236,7 +237,7 @@ public R post(String path, D data, Class cls, boolea request.setEntity(stringEntity); } HttpResponse response = client.execute(request, localContext); - getJenkinsVersionFromHeader(response); + jenkinsVersion = ResponseUtils.getJenkinsVersion(response); try { httpResponseValidator.validateResponse(response); @@ -264,14 +265,14 @@ public R post(String path, D data, Class cls, boolea * Perform a POST request using form url encoding. * * This method was added for the purposes of creating folders, but may be - * useful for other API calls as well. - * - * Unlike post and post_xml, the path is *not* modified by adding - * "/api/json". Additionally, the params in data are provided as both - * request parameters including a json parameter, *and* in the - * JSON-formatted StringEntity, because this is what the folder creation - * call required. It is unclear if any other jenkins APIs operate in this - * fashion. + useful for other API calls as well. + + Unlike post and post_xml, the path is *not* modified by adding + "/toJsonApiUri/json". Additionally, the params in data are provided as both + request parameters including a json parameter, *and* in the + JSON-formatted StringEntity, because this is what the folder creation + call required. It is unclear if any other jenkins APIs operate in this + fashion. * * @param path path to request, can be relative or absolute * @param data data to post @@ -291,10 +292,10 @@ public void post_form(String path, Map data, boolean crumbFlag) queryParams.add("json=" + EncodingUtils.encodeParam(JSONObject.fromObject(data).toString())); String value = mapper.writeValueAsString(data); StringEntity stringEntity = new StringEntity(value, ContentType.APPLICATION_FORM_URLENCODED); - request = new HttpPost(noapi(path) + StringUtils.join(queryParams, "&")); + request = new HttpPost(UrlUtils.toNoApiUri(uri, context, path) + StringUtils.join(queryParams, "&")); request.setEntity(stringEntity); } else { - request = new HttpPost(noapi(path)); + request = new HttpPost(UrlUtils.toNoApiUri(uri, context, path)); } if (crumbFlag == true) { @@ -305,7 +306,7 @@ public void post_form(String path, Map data, boolean crumbFlag) } HttpResponse response = client.execute(request, localContext); - getJenkinsVersionFromHeader(response); + jenkinsVersion = ResponseUtils.getJenkinsVersion(response); try { httpResponseValidator.validateResponse(response); @@ -331,10 +332,10 @@ public HttpResponse post_form_with_result(String path, List data, HttpPost request; if (data != null) { UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(data); - request = new HttpPost(noapi(path)); + request = new HttpPost(UrlUtils.toNoApiUri(uri, context, path)); request.setEntity(urlEncodedFormEntity); } else { - request = new HttpPost(noapi(path)); + request = new HttpPost(UrlUtils.toNoApiUri(uri, context, path)); } if (crumbFlag == true) { @@ -344,7 +345,7 @@ public HttpResponse post_form_with_result(String path, List data, } } HttpResponse response = client.execute(request, localContext); - getJenkinsVersionFromHeader(response); + jenkinsVersion = ResponseUtils.getJenkinsVersion(response); return response; } @@ -362,7 +363,7 @@ public String post_xml(String path, String xml_data) throws IOException { } public String post_xml(String path, String xml_data, boolean crumbFlag) throws IOException { - HttpPost request = new HttpPost(api(path)); + HttpPost request = new HttpPost(UrlUtils.toJsonApiUri(uri, context, path)); if (crumbFlag == true) { Crumb crumb = getQuietly("/crumbIssuer", Crumb.class); if (crumb != null) { @@ -374,7 +375,7 @@ public String post_xml(String path, String xml_data, boolean crumbFlag) throws I request.setEntity(new StringEntity(xml_data, ContentType.create("text/xml", "utf-8"))); } HttpResponse response = client.execute(request, localContext); - getJenkinsVersionFromHeader(response); + jenkinsVersion = ResponseUtils.getJenkinsVersion(response); try { httpResponseValidator.validateResponse(response); return IOUtils.toString(response.getEntity().getContent()); @@ -409,7 +410,7 @@ public String post_text(String path, String textData, boolean crumbFlag) throws */ public String post_text(String path, String textData, ContentType contentType, boolean crumbFlag) throws IOException { - HttpPost request = new HttpPost(api(path)); + HttpPost request = new HttpPost(UrlUtils.toJsonApiUri(uri, context, path)); if (crumbFlag == true) { Crumb crumb = get("/crumbIssuer", Crumb.class); if (crumb != null) { @@ -421,7 +422,7 @@ public String post_text(String path, String textData, ContentType contentType, b request.setEntity(new StringEntity(textData, contentType)); } HttpResponse response = client.execute(request, localContext); - getJenkinsVersionFromHeader(response); + jenkinsVersion = ResponseUtils.getJenkinsVersion(response); try { httpResponseValidator.validateResponse(response); return IOUtils.toString(response.getEntity().getContent()); @@ -445,35 +446,11 @@ public void post(String path, boolean crumbFlag) throws IOException { post(path, null, null, crumbFlag); } - private String urlJoin(String path1, String path2) { - if (!path1.endsWith("/")) { - path1 += "/"; - } - if (path2.startsWith("/")) { - path2 = path2.substring(1); - } - return path1 + path2; - } + - private URI api(String path) { - if (!path.toLowerCase().matches("https?://.*")) { - path = urlJoin(this.context, path); - } - if (!path.contains("?")) { - path = urlJoin(path, "api/json"); - } else { - String[] components = path.split("\\?", 2); - path = urlJoin(components[0], "api/json") + "?" + components[1]; - } - return uri.resolve("/").resolve(path.replace(" ", "%20")); - } + - private URI noapi(String path) { - if (!path.toLowerCase().matches("https?://.*")) { - path = urlJoin(this.context, path); - } - return uri.resolve("/").resolve(path); - } + private T objectFromResponse(Class cls, HttpResponse response) throws IOException { InputStream content = response.getEntity().getContent(); @@ -508,12 +485,7 @@ public String getJenkinsVersion() { public boolean isJenkinsVersionSet() { return !EMPTY_VERSION.equals( this.jenkinsVersion ); } - private void getJenkinsVersionFromHeader(HttpResponse response) { - Header[] headers = response.getHeaders("X-Jenkins"); - if (headers.length == 1) { - this.jenkinsVersion = headers[0].getValue(); - } - } + private void releaseConnection(HttpRequestBase httpRequestBase) { httpRequestBase.releaseConnection(); diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/client/util/ResponseUtils.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/client/util/ResponseUtils.java new file mode 100644 index 00000000..572ff4b6 --- /dev/null +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/client/util/ResponseUtils.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2013 Cosmin Stejerean, Karl Heinz Marbaise, and contributors. + * + * Distributed under the MIT license: http://opensource.org/licenses/MIT + */ +package com.offbytwo.jenkins.client.util; + +import org.apache.http.Header; +import org.apache.http.HttpResponse; + +/** + * + * @author Dell Green + */ +public final class ResponseUtils { + + /** + * Utility Class. + */ + private ResponseUtils() { + //do nothing + } + + + + + /** + * Get Jenkins version from supplied response if any. + * @param response the response + * @return the version or empty string + */ + public static String getJenkinsVersion(final HttpResponse response) { + final Header[] hdrs = response.getHeaders("X-Jenkins"); + return hdrs.length == 0 ? "" : hdrs[0].getValue(); + } + + +} diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/client/util/UrlUtils.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/client/util/UrlUtils.java new file mode 100644 index 00000000..6f8b17d9 --- /dev/null +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/client/util/UrlUtils.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2013 Cosmin Stejerean, Karl Heinz Marbaise, and contributors. + * + * Distributed under the MIT license: http://opensource.org/licenses/MIT + */ + +package com.offbytwo.jenkins.client.util; + +import com.offbytwo.jenkins.model.FolderJob; +import java.net.URI; + +/** + * Utility class for manipulating API paths. + * @author Dell Green + */ +public final class UrlUtils { + + /** + * The default size to use for string buffers. + */ + private static final int DEFAULT_BUFFER_SIZE = 64; + + /** + * Utility Class. + */ + private UrlUtils() { + //do nothing + } + + + + /** + * Helper to create a base url in case a folder is given + * @param folder the folder or {@code null} + * @return The created base url. + */ + public static String toBaseUrl(final FolderJob folder) { + return folder == null ? "/" : folder.getUrl(); + } + + + + /** + * Helper to create the base url for a job, with or without a given folder + * @param folder the folder or {@code null} + * @param jobName the name of the job. + * @return converted base url. + */ + public static String toJobBaseUrl(final FolderJob folder, final String jobName) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_SIZE); + sb.append(UrlUtils.toBaseUrl(folder)); + if (sb.charAt(sb.length() - 1) != '/') sb.append('/'); + sb.append("job/"); + final String[] jobNameParts = jobName.split("/"); + + for (int i = 0; i < jobNameParts.length; i++) { + sb.append(EncodingUtils.encode(jobNameParts[i])); + if (i != jobNameParts.length - 1) sb.append('/'); + } + return sb.toString(); + } + + + /** + * Helper to create the base url for a view, with or without a given folder + * @param folder the folder or {@code null} + * @param name the of the view. + * @return converted view url. + */ + public static String toViewBaseUrl(final FolderJob folder, final String name) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_SIZE); + final String base = UrlUtils.toBaseUrl(folder); + sb.append(base); + if (!base.endsWith("/")) sb.append('/'); + sb.append("view/") + .append(EncodingUtils.encode(name)); + return sb.toString(); + } + + + /** + * Parses the provided job name for folders to get the full path for the job. + * @param jobName the fullName of the job. + * @return the path of the job including folders if present. + */ + public static String toFullJobPath(final String jobName) { + final String[] parts = jobName.split("/"); + if (parts.length == 1) return parts[0]; + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_SIZE); + + for (int i = 0; i < parts.length; i++) { + sb.append(parts[i]); + if (i != parts.length -1) sb.append("/job/"); + } + return sb.toString(); + } + + + + /** + * Join two paths together taking into account leading/trailing slashes. + * @param path1 the first path + * @param path2 the second path + * @return the joins path + */ + public static String join(final String path1, final String path2) { + if (path1.isEmpty() && path2.isEmpty()) return ""; + if (path1.isEmpty() && !path2.isEmpty()) return path2; + if (path2.isEmpty() && !path1.isEmpty()) return path1; + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_SIZE); + sb.append(path1); + if (sb.charAt(sb.length() - 1) == '/') sb.setLength(sb.length() - 1); + if (path2.charAt(0) != '/') sb.append('/'); + sb.append(path2); + return sb.toString(); + } + + + + /** + * Create a JSON URI from the supplied parameters. + * @param uri the server URI + * @param context the server context if any + * @param path the specific API path + * @return new full URI instance + */ + public static URI toJsonApiUri(final URI uri, final String context, final String path) { + String p = path; + if (!p.matches("(?i)https?://.*")) p = join(context, p); + + if (!p.contains("?")) { + p = join(p, "api/json"); + } else { + final String[] components = p.split("\\?", 2); + p = join(components[0], "api/json") + "?" + components[1]; + } + return uri.resolve("/").resolve(p.replace(" ", "%20")); + } + + + + /** + * Create a URI from the supplied parameters. + * @param uri the server URI + * @param context the server context if any + * @param path the specific API path + * @return new full URI instance + */ + public static URI toNoApiUri(final URI uri, final String context, final String path) { + final String p = path.matches("(?i)https?://.*") ? path : join(context, path); + return uri.resolve("/").resolve(p); + } + + + +} diff --git a/jenkins-client/src/test/java/com/offbytwo/jenkins/client/JenkinsHttpClientTest.java b/jenkins-client/src/test/java/com/offbytwo/jenkins/client/JenkinsHttpClientTest.java new file mode 100644 index 00000000..a55e0560 --- /dev/null +++ b/jenkins-client/src/test/java/com/offbytwo/jenkins/client/JenkinsHttpClientTest.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2013 Cosmin Stejerean, Karl Heinz Marbaise, and contributors. + * + * Distributed under the MIT license: http://opensource.org/licenses/MIT + */ +package com.offbytwo.jenkins.client; + +import java.io.ByteArrayInputStream; +import java.net.URI; +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpStatus; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.protocol.HttpContext; +import static org.junit.Assert.assertEquals; +import org.junit.Test; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; + + + +/** + * + * @author Dell Green + */ +public class JenkinsHttpClientTest { + private static final String URI = "http://localhost/jenkins"; + + + + @Test + public void testGet_String() throws Exception { + final CloseableHttpClient client = mock(CloseableHttpClient.class); + final CloseableHttpResponse response = mock(CloseableHttpResponse.class); + final Header versionHeader = mock(Header.class); + final StatusLine statusLine = mock(StatusLine.class); + final HttpEntity entity = mock(HttpEntity.class); + given(client.execute(any(HttpUriRequest.class), any(HttpContext.class))).willReturn(response); + given(response.getHeaders(anyString())).willReturn(new Header[]{versionHeader}); + given(response.getStatusLine()).willReturn(statusLine); + given(versionHeader.getValue()).willReturn("1.234"); + given(statusLine.getStatusCode()).willReturn(HttpStatus.SC_OK); + given(response.getEntity()).willReturn(entity); + given(entity.getContent()).willReturn(new ByteArrayInputStream("someJson".getBytes())); + final JenkinsHttpClient jclient = new JenkinsHttpClient(new URI(URI), client); + final String s = jclient.get("job/someJob"); + assertEquals("someJson", s); + } + + +} \ No newline at end of file diff --git a/jenkins-client/src/test/java/com/offbytwo/jenkins/client/util/ResponseUtilsTest.java b/jenkins-client/src/test/java/com/offbytwo/jenkins/client/util/ResponseUtilsTest.java new file mode 100644 index 00000000..57e001ea --- /dev/null +++ b/jenkins-client/src/test/java/com/offbytwo/jenkins/client/util/ResponseUtilsTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2013 Cosmin Stejerean, Karl Heinz Marbaise, and contributors. + * + * Distributed under the MIT license: http://opensource.org/licenses/MIT + */ +package com.offbytwo.jenkins.client.util; + +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import static org.junit.Assert.assertEquals; +import org.junit.Test; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + + + +/** + * + * @author Dell Green + */ +public class ResponseUtilsTest { + + + + @Test + public void testGetJenkinsVersion() { + final Header header = mock(Header.class); + final HttpResponse response = mock(HttpResponse.class); + given(response.getHeaders("X-Jenkins")).willReturn(new Header[]{header}); + given(header.getValue()).willReturn("1.234"); + assertEquals("1.234", ResponseUtils.getJenkinsVersion(response)); + } + + + @Test + public void testGetJenkinsVersion_NoHeader() { + final HttpResponse response = mock(HttpResponse.class); + given(response.getHeaders("X-Jenkins")).willReturn(new Header[0]); + assertEquals("", ResponseUtils.getJenkinsVersion(response)); + } + + + @Test(expected = NullPointerException.class) + public void testGetJenkinsVersion_NullResponse() { + ResponseUtils.getJenkinsVersion(null); + } + + +} \ No newline at end of file diff --git a/jenkins-client/src/test/java/com/offbytwo/jenkins/client/util/UrlUtilsTest.java b/jenkins-client/src/test/java/com/offbytwo/jenkins/client/util/UrlUtilsTest.java new file mode 100644 index 00000000..780600ba --- /dev/null +++ b/jenkins-client/src/test/java/com/offbytwo/jenkins/client/util/UrlUtilsTest.java @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2013 Cosmin Stejerean, Karl Heinz Marbaise, and contributors. + * + * Distributed under the MIT license: http://opensource.org/licenses/MIT + */ +package com.offbytwo.jenkins.client.util; + +import com.offbytwo.jenkins.model.FolderJob; +import java.net.URI; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import org.junit.Test; + + + +/** + * + * @author Dell Green + */ +public class UrlUtilsTest { + + + + @Test + public void testToBaseUrl_NullFolderJob() { + assertEquals("/", UrlUtils.toBaseUrl(null)); + } + + @Test + public void testToBaseUrl_DefaultFolderJob() { + assertNull("/", UrlUtils.toBaseUrl(new FolderJob())); + } + + + @Test + public void testToBaseUrl() { + final String fpath = "http://localhost/jobs/someFolder/"; + final FolderJob folderJob = new FolderJob("someFolder", fpath); + assertEquals(fpath, UrlUtils.toBaseUrl(folderJob)); + } + + + @Test + public void testToJobBaseUrl() { + final String fpath = "http://localhost/jobs/someFolder/"; + final FolderJob folderJob = new FolderJob("someFolder", fpath); + final String expected = "http://localhost/jobs/someFolder/job/someJob"; + assertEquals(expected, UrlUtils.toJobBaseUrl(folderJob, "someJob")); + } + + @Test + public void testToJobBaseUrl_NoTrailingFolderSlash() { + final String fpath = "http://localhost/jobs/someFolder"; + final FolderJob folderJob = new FolderJob("someFolder", fpath); + final String expected = "http://localhost/jobs/someFolder/job/someJob"; + assertEquals(expected, UrlUtils.toJobBaseUrl(folderJob, "someJob")); + } + + + @Test + public void testToJobBaseUrl_NullFolderJob() { + assertEquals("/job/someJob", UrlUtils.toJobBaseUrl(null, "someJob")); + } + + + @Test + public void testToJobBaseUrl_EmptyJobName() { + final String fpath = "http://localhost/jobs/someFolder"; + final FolderJob folderJob = new FolderJob("someFolder", fpath); + final String expected = "http://localhost/jobs/someFolder/job/"; + assertEquals(expected, UrlUtils.toJobBaseUrl(folderJob, "")); + } + + @Test(expected = NullPointerException.class) + public void testToJobBaseUrl_NullJobName() { + final String fpath = "http://localhost/jobs/someFolder"; + final FolderJob folderJob = new FolderJob("someFolder", fpath); + UrlUtils.toJobBaseUrl(folderJob, null); + } + + + @Test + public void testToViewBaseUrl() { + final String fpath = "http://localhost/jobs/someFolder/"; + final FolderJob folderJob = new FolderJob("someFolder", fpath); + final String expected = "http://localhost/jobs/someFolder/view/someView"; + assertEquals(expected, UrlUtils.toViewBaseUrl(folderJob, "someView")); + } + + @Test + public void testToViewBaseUrl_NoTrailingFolderSlash() { + final String fpath = "http://localhost/jobs/someFolder"; + final FolderJob folderJob = new FolderJob("someFolder", fpath); + final String expected = "http://localhost/jobs/someFolder/view/someView"; + assertEquals(expected, UrlUtils.toViewBaseUrl(folderJob, "someView")); + } + + + @Test + public void testToViewBaseUrl_NullFolderJob() { + assertEquals("/view/someView", UrlUtils.toViewBaseUrl(null, "someView")); + } + + + @Test + public void testToViewBaseUrl_EmptyViewName() { + final String fpath = "http://localhost/jobs/someFolder"; + final FolderJob folderJob = new FolderJob("someFolder", fpath); + final String expected = "http://localhost/jobs/someFolder/view/"; + assertEquals(expected, UrlUtils.toViewBaseUrl(folderJob, "")); + } + + @Test(expected = NullPointerException.class) + public void testToViewBaseUrl_NullViewName() { + final String fpath = "http://localhost/jobs/someFolder"; + final FolderJob folderJob = new FolderJob("someFolder", fpath); + UrlUtils.toViewBaseUrl(folderJob, null); + } + + + @Test + public void testToFullJobPath_JobNameOnly() { + assertEquals("someJob", UrlUtils.toFullJobPath("someJob")); + } + + + @Test + public void testToFullJobPath_JobNameWithSingleFolder() { + final String expected = "someFolder/job/someJob"; + assertEquals(expected, UrlUtils.toFullJobPath("someFolder/someJob")); + } + + @Test + public void testToFullJobPath_JobNameWithMultipleFolders() { + final String expected = "someFolder1/job/someFolder2/job/someJob"; + assertEquals(expected, UrlUtils.toFullJobPath("someFolder1/someFolder2/someJob")); + } + + @Test(expected = NullPointerException.class) + public void testToFullJobPath_NullJobName() { + UrlUtils.toFullJobPath(null); + } + + + @Test + public void testToFullJobPath_EmptyJobName() { + assertEquals("", UrlUtils.toFullJobPath("")); + } + + + @Test + public void testJoin_EmptyBothPaths() { + assertEquals("", UrlUtils.join("", "")); + } + + @Test + public void testJoin_SlashesOnly() { + assertEquals("/", UrlUtils.join("/", "/")); + } + + @Test + public void testJoin_EmptyPath2() { + assertEquals("1/2/3", UrlUtils.join("1/2/3", "")); + } + + @Test + public void testJoin_EmptyPath1() { + assertEquals("4/5/6", UrlUtils.join("", "4/5/6")); + } + + @Test + public void testJoin_NoTrailingLeadingSlashes() { + assertEquals("1/2/3/4/5/6", UrlUtils.join("1/2/3", "4/5/6")); + + } + + @Test + public void testJoin_TrailingLeadingSlashes() { + assertEquals("/1/2/3/4/5/6/", UrlUtils.join("/1/2/3/", "/4/5/6/")); + } + + @Test + public void testJoin_Path1Trailing_Path2NoLeading() { + assertEquals("/1/2/3/4/5/6/", UrlUtils.join("/1/2/3/", "4/5/6/")); + } + + @Test + public void testJoin_Path1NoTrailing_Path2Leading() { + assertEquals("/1/2/3/4/5/6/", UrlUtils.join("/1/2/3", "/4/5/6/")); + } + + + + + + @Test(expected = NullPointerException.class) + public void testJoin_NullPath1() { + UrlUtils.join(null, "4/5/6"); + } + + @Test(expected = NullPointerException.class) + public void testJoin_NullPath2() { + UrlUtils.join("1/2/3", null); + } + + + @Test + public void testToQueryUri_WithoutQuery() throws Exception { + final String suri = "http://localhost/jenkins"; + final URI uri = UrlUtils.toJsonApiUri(new URI(suri), "jenkins", "job/somejob"); + final String expected = "http://localhost/jenkins/job/somejob/api/json"; + assertEquals(expected, uri.toString()); + } + + @Test + public void testToQueryUri_EmptyContext() throws Exception { + final String suri = "http://localhost/jenkins"; + final URI uri = UrlUtils.toJsonApiUri(new URI(suri), "", "job/somejob"); + final String expected = "http://localhost/job/somejob/api/json"; + assertEquals(expected, uri.toString()); + } + + @Test + public void testToQueryUri_EmptyPathAndContext() throws Exception { + final String suri = "http://localhost/jenkins"; + final URI uri = UrlUtils.toJsonApiUri(new URI(suri), "", ""); + final String expected = "http://localhost/api/json"; + assertEquals(expected, uri.toString()); + } + + @Test(expected = NullPointerException.class) + public void testToQueryUri_NullUri() throws Exception { + UrlUtils.toJsonApiUri(null, "jenkins", "job/somejob"); + } + + + @Test(expected = NullPointerException.class) + public void testToQueryUri_NullPath() throws Exception { + UrlUtils.toJsonApiUri(new URI("http://localhost/jenkins"), "jenkins", null); + } + + @Test(expected = NullPointerException.class) + public void testToQueryUri_NullContext() throws Exception { + UrlUtils.toJsonApiUri(new URI("http://localhost/jenkins"), null, "job/ajob"); + } + + @Test + public void testToQueryUri_EmptyPath() throws Exception { + final String suri = "http://localhost/jenkins"; + final URI uri = UrlUtils.toJsonApiUri(new URI(suri), "jenkins", ""); + final String expected = "http://localhost/jenkins/api/json"; + assertEquals(expected, uri.toString()); + } + + @Test + public void testToQueryUri_UpperCase() throws Exception { + final String suri = "HTTP://localhost/jenkins"; + final URI uri = UrlUtils.toJsonApiUri(new URI(suri), "jenkins", "job/somejob"); + final String expected = "HTTP://localhost/jenkins/job/somejob/api/json"; + assertEquals(expected, uri.toString()); + } + + + @Test + public void testToQueryUri_WithQuery() throws Exception { + final String suri = "http://localhost/jenkins"; + final URI uri = UrlUtils.toJsonApiUri(new URI(suri), "jenkins", "job/somejob?pretty=true"); + final String expected = "http://localhost/jenkins/job/somejob/api/json?pretty=true"; + assertEquals(expected, uri.toString()); + } + + + + @Test + public void testToQueryUri_PathContainsSchemeAndContext() throws Exception { + final String suri = "http://localhost/jenkins"; + final URI uri = UrlUtils.toJsonApiUri(new URI(suri), "jenkins", "http://localhost/jenkins/job/somejob"); + final String expected = "http://localhost/jenkins/job/somejob/api/json"; + assertEquals(expected, uri.toString()); + } + + + @Test + public void testToNoQueryUri_PathContainsSchemeAndContext() throws Exception { + final String suri = "http://localhost/jenkins"; + final URI uri = UrlUtils.toNoApiUri(new URI(suri), "jenkins", "http://localhost/jenkins/job/somejob"); + final String expected = "http://localhost/jenkins/job/somejob"; + assertEquals(expected, uri.toString()); + } + + + @Test + public void testToNoQueryUri_UpperCase() throws Exception { + final String suri = "HTTP://localhost/jenkins"; + final URI uri = UrlUtils.toNoApiUri(new URI(suri), "jenkins", "job/somejob"); + final String expected = "HTTP://localhost/jenkins/job/somejob"; + assertEquals(expected, uri.toString()); + } + + + @Test + public void testToNoQueryUri_EmptyPath() throws Exception { + final String suri = "http://localhost/jenkins"; + final URI uri = UrlUtils.toNoApiUri(new URI(suri), "jenkins", ""); + final String expected = "http://localhost/jenkins"; + assertEquals(expected, uri.toString()); + } + + + @Test + public void testToNoQueryUri_EmptyContext() throws Exception { + final String suri = "http://localhost/jenkins"; + final URI uri = UrlUtils.toNoApiUri(new URI(suri), "", "job/somejob"); + final String expected = "http://localhost/job/somejob"; + assertEquals(expected, uri.toString()); + } + + + @Test(expected = NullPointerException.class) + public void testToNoQueryUri_NullContext() throws Exception { + UrlUtils.toNoApiUri(new URI("http://localhost/jenkins"), null, "job/ajob"); + } + +} \ No newline at end of file