diff --git a/src/main/java/org/kohsuke/github/GHLicense.java b/src/main/java/org/kohsuke/github/GHLicense.java new file mode 100644 index 000000000..baeb6c3d7 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHLicense.java @@ -0,0 +1,110 @@ +/* + * The MIT License + * + * Copyright (c) 2016, Duncan Dickinson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.kohsuke.github; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +/** + * The GitHub Preview API's license information + *

+ * WARNING: This uses a PREVIEW API - you must use {@link org.kohsuke.github.extras.PreviewHttpConnector} + * + * @author Duncan Dickinson + * @see GitHub#getLicense(String) + * @see GHRepository#getFullLicense() + * @see https://developer.github.com/v3/licenses/ + */ +@SuppressWarnings({"UnusedDeclaration"}) +@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", + "NP_UNWRITTEN_FIELD"}, justification = "JSON API") +public class GHLicense extends GHLicenseBase { + + protected String html_url, description, category, implementation, body; + + protected List required = new ArrayList(); + protected List permitted = new ArrayList(); + protected List forbidden = new ArrayList(); + + public URL getHtmlUrl() { + return GitHub.parseURL(html_url); + } + + public String getDescription() { + return description; + } + + public String getCategory() { + return category; + } + + public String getImplementation() { + return implementation; + } + + public List getRequired() { + return required; + } + + public List getPermitted() { + return permitted; + } + + public List getForbidden() { + return forbidden; + } + + public String getBody() { + return body; + } + + @Override + public String toString() { + return "GHLicense{" + + "html_url='" + html_url + '\'' + + ", description='" + description + '\'' + + ", category='" + category + '\'' + + ", implementation='" + implementation + '\'' + + ", body='" + body + '\'' + + ", required=" + required + + ", permitted=" + permitted + + ", forbidden=" + forbidden + + ", htmlUrl=" + getHtmlUrl() + + "} " + super.toString(); + } + + @Override + public boolean equals(Object o) { + return super.equals(o); + } + + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/src/main/java/org/kohsuke/github/GHLicenseBase.java b/src/main/java/org/kohsuke/github/GHLicenseBase.java new file mode 100644 index 000000000..e6c92359d --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHLicenseBase.java @@ -0,0 +1,107 @@ +/* + * The MIT License + * + * Copyright (c) 2016, Duncan Dickinson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.kohsuke.github; + +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.net.URL; + +/** + * The basic information for GitHub API licenses - as use in a number of + * API calls that only return the basic details + *

+ * WARNING: This uses a PREVIEW API - you must use {@link org.kohsuke.github.extras.PreviewHttpConnector} + * + * @author Duncan Dickinson + * @see https://developer.github.com/v3/licenses/ + * @see GitHub#listLicenses() + * @see GHRepository#getLicense() + * @see GHLicense GHLicense subclass for the more comprehensive listing of properties + */ +@SuppressWarnings({"UnusedDeclaration"}) +@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", + "NP_UNWRITTEN_FIELD"}, justification = "JSON API") +public class GHLicenseBase { + + protected String key, name, url; + protected Boolean featured; + + /** + * @return a mnemonic for the license + */ + public String getKey() { + return key; + } + + /** + * @return the license name + */ + public String getName() { + return name; + } + + /** + * @return API URL of this object. + */ + @WithBridgeMethods(value = String.class, adapterMethod = "urlToString") + public URL getUrl() { + return GitHub.parseURL(url); + } + + /** + * Featured licenses are bold in the new repository drop-down + * + * @return True if the license is featured, false otherwise + */ + public Boolean isFeatured() { + return featured; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof GHLicenseBase)) return false; + + GHLicenseBase that = (GHLicenseBase) o; + + return getUrl().toString().equals(that.getUrl().toString()); + } + + @Override + public int hashCode() { + return getUrl().toString().hashCode(); + } + + @Override + public String toString() { + return "GHLicenseBase{" + + "key='" + key + '\'' + + ", name='" + name + '\'' + + ", url='" + url + '\'' + + ", featured=" + featured + + '}'; + } +} diff --git a/src/main/java/org/kohsuke/github/GHRepository.java b/src/main/java/org/kohsuke/github/GHRepository.java index 41a44e9da..0b7424311 100644 --- a/src/main/java/org/kohsuke/github/GHRepository.java +++ b/src/main/java/org/kohsuke/github/GHRepository.java @@ -65,6 +65,16 @@ public class GHRepository extends GHObject { private String description, homepage, name, full_name; private String html_url; // this is the UI + /* + * The license information makes use of the preview API. + * + * See: https://developer.github.com/v3/licenses/ + */ + /** + * The basic license details as returned from {@link GitHub#getRepository(String)} + */ + private GHLicenseBase license; + private String git_url, ssh_url, clone_url, svn_url, mirror_url; private GHUser owner; // not fully populated. beware. private boolean has_issues, has_wiki, fork, has_downloads; @@ -839,6 +849,44 @@ public class GHRepository extends GHObject { } }; } + * Gets the basic license details for the repository. + *

+ * This is a preview item and requires you to use {@link org.kohsuke.github.extras.PreviewHttpConnector} + *

+ * Warning: Only returns the basic license details. Use {@link GitHub#getLicense(String)} + * to get the full license information (hint: pass it {@link GHLicenseBase#getKey()}). + * + * @throws IOException as usual but also if you don't use the preview connector + */ + public GHLicenseBase getLicense() { + return license; + } + + /** + * Access the full license details - makes an additional API call + *

+ * This is a preview item and requires you to use {@link org.kohsuke.github.extras.PreviewHttpConnector} + * + * @return the license details + * @throws IOException as usual but also if you don't use the preview connector + */ + public GHLicense getFullLicense() throws IOException { + return root.getLicense(license.getKey()); + } + + /** + * Retrieves the contents of the repository's license file - makes an additional API call + *

+ * This is a preview item and requires you to use {@link org.kohsuke.github.extras.PreviewHttpConnector} + * + * @return details regarding the license contents + * @throws IOException as usual but also if you don't use the preview connector + */ + public GHContent getLicenseContent() throws IOException { + return root.retrieve().to(getApiTailUrl("license"), GHContent.class).wrap(this); + } + + /** /** * Lists all the commit statues attached to the given commit, newer ones first. diff --git a/src/main/java/org/kohsuke/github/GitHub.java b/src/main/java/org/kohsuke/github/GitHub.java index 297596d87..dd1e3593e 100644 --- a/src/main/java/org/kohsuke/github/GitHub.java +++ b/src/main/java/org/kohsuke/github/GitHub.java @@ -56,6 +56,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.introspect.VisibilityChecker.Std; import com.infradna.tool.bridge_method_injector.WithBridgeMethods; + import java.util.logging.Logger; /** @@ -339,6 +340,33 @@ public class GitHub { String[] tokens = name.split("/"); return retrieve().to("/repos/" + tokens[0] + '/' + tokens[1], GHRepository.class).wrap(this); } + * Returns a list of popular open source licenses + * + * WARNING: This uses a PREVIEW API - you must use {@link org.kohsuke.github.extras.PreviewHttpConnector} + * + * @see GitHub API - Licenses + * + * @return a list of popular open source licenses + * @throws IOException if the HttpConnector doesn't pass in the preview header or other IO issue + */ + public List listLicenses() throws IOException { + return Arrays.asList(retrieve().to("/licenses", GHLicenseBase[].class)); + } + + /** + * Returns the full details for a license + * + * WARNING: This uses a PREVIEW API - you must use {@link org.kohsuke.github.extras.PreviewHttpConnector} + * + * @param key The license key provided from the API + * @return The license details + * @throws IOException + */ + public GHLicense getLicense(String key) throws IOException { + return retrieve().to("/licenses/" + key, GHLicense.class); + } + + /** /** * This method returns a shallowly populated organizations. diff --git a/src/main/java/org/kohsuke/github/extras/PreviewHttpConnector.java b/src/main/java/org/kohsuke/github/extras/PreviewHttpConnector.java new file mode 100644 index 000000000..e1be7855f --- /dev/null +++ b/src/main/java/org/kohsuke/github/extras/PreviewHttpConnector.java @@ -0,0 +1,67 @@ +/* + * Copyright $year slavinson + * + * 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 org.kohsuke.github.extras; + +import org.kohsuke.github.HttpConnector; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; + +public class PreviewHttpConnector implements HttpConnector { + private final HttpConnector base; + private final int readTimeout, connectTimeout; + + /** + * @param connectTimeout HTTP connection timeout in milliseconds + * @param readTimeout HTTP read timeout in milliseconds + */ + public PreviewHttpConnector(HttpConnector base, int connectTimeout, int readTimeout) { + this.base = base; + this.connectTimeout = connectTimeout; + this.readTimeout = readTimeout; + } + + public PreviewHttpConnector(HttpConnector base, int timeout) { + this(base, timeout, timeout); + } + + public PreviewHttpConnector(HttpConnector base) { + this(base, ImpatientHttpConnector.CONNECT_TIMEOUT, ImpatientHttpConnector.READ_TIMEOUT); + } + + public PreviewHttpConnector() { + this(new HttpConnector() { + public HttpURLConnection connect(URL url) throws IOException { + return (HttpURLConnection) url.openConnection(); + } + }, ImpatientHttpConnector.CONNECT_TIMEOUT, ImpatientHttpConnector.READ_TIMEOUT); + } + + public HttpURLConnection connect(URL url) throws IOException { + HttpURLConnection con = base.connect(url); + con.setConnectTimeout(connectTimeout); + con.setReadTimeout(readTimeout); + con.addRequestProperty("Accept", PREVIEW_MEDIA_TYPE); + return con; + } + + /** + * Default connection timeout in milliseconds + */ + public static final String PREVIEW_MEDIA_TYPE = "application/vnd.github.drax-preview+json"; +} diff --git a/src/test/java/org/kohsuke/github/GHLicenseTest.java b/src/test/java/org/kohsuke/github/GHLicenseTest.java new file mode 100644 index 000000000..e27f8057c --- /dev/null +++ b/src/test/java/org/kohsuke/github/GHLicenseTest.java @@ -0,0 +1,220 @@ +/* + * The MIT License + * + * Copyright (c) 2016, Duncan Dickinson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.kohsuke.github; + +import org.apache.commons.io.IOUtils; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.kohsuke.github.extras.PreviewHttpConnector; + +import java.io.IOException; +import java.net.URL; +import java.util.List; + +/** + * @author Duncan Dickinson + */ +public class GHLicenseTest extends Assert { + private GitHub gitHub; + + @Before + public void setUp() throws Exception { + gitHub = new GitHubBuilder() + .fromCredentials() + .withConnector(new PreviewHttpConnector()) + .build(); + } + + /** + * Basic test to ensure that the list of licenses from {@link GitHub#listLicenses()} is returned + * + * @throws IOException + */ + @Test + public void listLicenses() throws IOException { + List licenses = gitHub.listLicenses(); + assertTrue(licenses.size() > 0); + } + + /** + * Tests that {@link GitHub#listLicenses()} returns the MIT license + * in the expected manner. + * + * @throws IOException + */ + @Test + public void listLicensesCheckIndividualLicense() throws IOException { + List licenses = gitHub.listLicenses(); + for (GHLicenseBase lic : licenses) { + if (lic.getKey().equals("mit")) { + assertTrue(lic.getUrl().equals(new URL("https://api.github.com/licenses/mit"))); + return; + } + } + fail("The MIT license was not found"); + } + + /** + * Checks that the request for an individual license using {@link GitHub#getLicense(String)} + * returns expected values (not all properties are checked) + * + * @throws IOException + */ + @Test + public void getLicense() throws IOException { + String key = "mit"; + GHLicense license = gitHub.getLicense(key); + assertNotNull(license); + assertTrue("The name is correct", license.getName().equals("MIT License")); + assertTrue("The HTML URL is correct", license.getHtmlUrl().equals(new URL("http://choosealicense.com/licenses/mit/"))); + } + + /** + * Attempts to list the licenses with a non-preview connection + * + * @throws IOException is expected to be thrown + */ + @Test(expected = IOException.class) + public void ListLicensesWithoutPreviewConnection() throws IOException { + GitHub.connect().listLicenses(); + } + + /** + * Accesses the 'kohsuke/github-api' repo using {@link GitHub#getRepository(String)} + * and checks that the license is correct + * + * @throws IOException + */ + @Test + public void checkRepositoryLicense() throws IOException { + GHRepository repo = gitHub.getRepository("kohsuke/github-api"); + GHLicenseBase license = repo.getLicense(); + assertNotNull("The license is populated", license); + assertTrue("The key is correct", license.getKey().equals("mit")); + assertTrue("The name is correct", license.getName().equals("MIT License")); + assertTrue("The URL is correct", license.getUrl().equals(new URL("https://api.github.com/licenses/mit"))); + } + + /** + * Accesses the 'atom/atom' repo using {@link GitHub#getRepository(String)} + * and checks that the license is correct + * + * @throws IOException + */ + @Test + public void checkRepositoryLicenseAtom() throws IOException { + GHRepository repo = gitHub.getRepository("atom/atom"); + GHLicenseBase license = repo.getLicense(); + assertNotNull("The license is populated", license); + assertTrue("The key is correct", license.getKey().equals("mit")); + assertTrue("The name is correct", license.getName().equals("MIT License")); + assertTrue("The URL is correct", license.getUrl().equals(new URL("https://api.github.com/licenses/mit"))); + } + + /** + * Accesses the 'pomes/pomes' repo using {@link GitHub#getRepository(String)} + * and checks that the license is correct + * + * @throws IOException + */ + @Test + public void checkRepositoryLicensePomes() throws IOException { + GHRepository repo = gitHub.getRepository("pomes/pomes"); + GHLicenseBase license = repo.getLicense(); + assertNotNull("The license is populated", license); + assertTrue("The key is correct", license.getKey().equals("apache-2.0")); + assertTrue("The name is correct", license.getName().equals("Apache License 2.0")); + assertTrue("The URL is correct", license.getUrl().equals(new URL("https://api.github.com/licenses/apache-2.0"))); + } + + /** + * Accesses the 'dedickinson/test-repo' repo using {@link GitHub#getRepository(String)} + * and checks that *no* license is returned as the repo doesn't have one + * + * @throws IOException + */ + @Test + public void checkRepositoryWithoutLicense() throws IOException { + GHRepository repo = gitHub.getRepository("dedickinson/test-repo"); + GHLicenseBase license = repo.getLicense(); + assertNull("There is no license", license); + } + + /** + * Accesses the 'kohsuke/github-api' repo using {@link GitHub#getRepository(String)} + * and then calls {@link GHRepository#getFullLicense()} and checks that certain + * properties are correct + * + * @throws IOException + */ + @Test + public void checkRepositoryFullLicense() throws IOException { + GHRepository repo = gitHub.getRepository("kohsuke/github-api"); + GHLicense license = repo.getFullLicense(); + assertNotNull("The license is populated", license); + assertTrue("The key is correct", license.getKey().equals("mit")); + assertTrue("The name is correct", license.getName().equals("MIT License")); + assertTrue("The URL is correct", license.getUrl().equals(new URL("https://api.github.com/licenses/mit"))); + assertTrue("The HTML URL is correct", license.getHtmlUrl().equals(new URL("http://choosealicense.com/licenses/mit/"))); + } + + /** + * Accesses the 'pomes/pomes' repo using {@link GitHub#getRepository(String)} + * and then calls {@link GHRepository#getLicenseContent()} and checks that certain + * properties are correct + * + * @throws IOException + */ + @Test + public void checkRepositoryLicenseContent() throws IOException { + GHRepository repo = gitHub.getRepository("pomes/pomes"); + GHContent content = repo.getLicenseContent(); + assertNotNull("The license content is populated", content); + assertTrue("The type is 'file'", content.getType().equals("file")); + assertTrue("The license file is 'LICENSE'", content.getName().equals("LICENSE")); + + if (content.getEncoding().equals("base64")) { + String licenseText = new String(IOUtils.toByteArray(content.read())); + assertTrue("The license appears to be an Apache License", licenseText.contains("Apache License")); + } else { + fail("Expected the license to be Base64 encoded but instead it was " + content.getEncoding()); + } + } + + /** + * Accesses the 'kohsuke/github-api' repo using {@link GitHub#getRepository(String)} + * but without using {@link PreviewHttpConnector} and ensures that the {@link GHRepository#getLicense()} + * call just returns null rather than raising an exception. This should indicate that + * non-preview connection requests aren't affected by the change in functionality + * + * @throws IOException + */ + @Test + public void checkRepositoryLicenseWithoutPreviewConnection() throws IOException { + GHRepository repo = GitHub.connect().getRepository("kohsuke/github-api"); + assertNull(repo.getLicense()); + } +}