Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better error messages #254

Merged
merged 2 commits into from
Mar 12, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/org/kohsuke/github/GitHub.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
Expand All @@ -55,6 +56,8 @@
import com.fasterxml.jackson.databind.introspect.VisibilityChecker.Std;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import java.nio.charset.Charset;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Root of the GitHub API.
Expand All @@ -68,6 +71,8 @@
* @author Kohsuke Kawaguchi
*/
public class GitHub {
protected final transient Logger logger = Logger.getLogger(getClass().getName());

/*package*/ final String login;

/**
Expand Down Expand Up @@ -458,6 +463,8 @@ public boolean isCredentialValid() throws IOException {
retrieve().to("/user", GHUser.class);
return true;
} catch (IOException e) {
if (logger.isLoggable(Level.FINE))
logger.log(Level.FINE, "Exception validating credentials on " + this.apiUrl + " with login '" + this.login + "' " + e, e);
return false;
}
}
Expand Down
118 changes: 118 additions & 0 deletions src/main/java/org/kohsuke/github/HttpException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package org.kohsuke.github;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;

import javax.annotation.CheckForNull;

/**
* {@link IOException} for http exceptions because {@link HttpURLConnection} throws un-discerned
* {@link IOException} and it can help to know the http response code to decide how to handle an
* http exceptions.
*
* @author <a href="mailto:[email protected]">Cyrille Le Clerc</a>
*/
public class HttpException extends IOException {
static final long serialVersionUID = 1L;

private final int responseCode;
private final String responseMessage;
private final String url;

/**
* @param message The detail message (which is saved for later retrieval
* by the {@link #getMessage()} method)
* @param responseCode Http response code. {@code -1} if no code can be discerned.
* @param responseMessage Http response message
* @param url The url that was invoked
* @see HttpURLConnection#getResponseCode()
* @see HttpURLConnection#getResponseMessage()
*/
public HttpException(String message, int responseCode, String responseMessage, String url) {
super(message);
this.responseCode = responseCode;
this.responseMessage = responseMessage;
this.url = url;
}

/**
* @param message The detail message (which is saved for later retrieval
* by the {@link #getMessage()} method)
* @param responseCode Http response code. {@code -1} if no code can be discerned.
* @param responseMessage Http response message
* @param url The url that was invoked
* @param cause The cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A null value is permitted,
* and indicates that the cause is nonexistent or unknown.)
* @see HttpURLConnection#getResponseCode()
* @see HttpURLConnection#getResponseMessage()
*/
public HttpException(String message, int responseCode, String responseMessage, String url, Throwable cause) {
super(message, cause);
this.responseCode = responseCode;
this.responseMessage = responseMessage;
this.url = url;
}

/**
* @param message The detail message (which is saved for later retrieval
* by the {@link #getMessage()} method)
* @param responseCode Http response code. {@code -1} if no code can be discerned.
* @param responseMessage Http response message
* @param url The url that was invoked
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intended to be @CheckForNull? The next overload seems to imply that it is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed deserves a @CheckForNull but as @CheckForNull and @Nonnull are not used in this project and as we only have javax.annotation.CheckForNull that @stephenc don't like, I did not dare to add it.

I am happy to add @javax.annotation.CheckForNull.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not a maintainer of this plugin, so if the plugin maintainers want to have you use an annotation that is - in its current source - illegal under the terms of the JCP and may put you in breech of contract if you ever signed a JCP agreement, that is your and their business

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephenc can you please remind me which annotation library is "the right one" and I'll commit the change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to ask the owner of this codebase what one they prefer.

Now as to which is correct:

  • you are only allowed to use the javax package for authoring classes that you redistribute where the spec of those classes has been published via the JCP process.
  • the JSR that was working on annotations to use for eg null status has not published anything at all. The only public statement is that they might introduce a javax.annotations.NonNull (which is not the one everyone is using you will note)
  • as such we are breaking the good faith of the EULA or JCP etc agreements that maybe could - at an extreme stretch - be used to initiate proceedings... I doubt such proceedings would win... But then look at the Google - Oracle API appeal

So where I have a choice I will use the edu. version... under the non GPL license... Because in those cases I can choose to "do the right thing"... If there is ever even an official published draft of annotations for this in 'javax` I will switch to them

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephenc since you seem to have a strong opinion would you mind proposing a patch to plugin-pom to forbid usage of the one(s) you do not like and/or enforce usage of the one(s) you do like, bearing in mind that edu.umd.cs.findbugs.annotations.NonNull was @Deprecated in com.google.code.findbugs:annotations:3.0.0 and that com.github.stephenc.findbugs:findbugs-annotations:1.3.9-1 is clearly outdated?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way I can guess that the owner of this codebase is not really interested in the question and you can use whatever you find convenient.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephenc thanks, I am a bit stuck, net.sourceforge.findbugs:annotations is LGPL and thus incompatible with the MIT license of this project. com.github.stephenc.findbugs:findbugs-annotations is ASL2 and thus could be OK.

I'll wait for the owner of the code base to make a suggestion :-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I discussed this subject with the owner of the code base, and the owner has agreed that they personally will start making an effort to use the edu one.

I will merge the PR on my ASL clean room version of find bugs annotations tomorrow and release into central (without @Deprecacted)

What you choose to do is up to you

Sent from my iPhone

On 7 Mar 2016, at 20:26, Cyrille Le Clerc [email protected] wrote:

In src/main/java/org/kohsuke/github/HttpException.java:

  • \* @see HttpURLConnection#getResponseCode()
    
  • \* @see HttpURLConnection#getResponseMessage()
    
  • */
    
  • public HttpException(String message, int responseCode, String responseMessage, String url, Throwable cause) {
  •    super(message, cause);
    
  •    this.responseCode = responseCode;
    
  •    this.responseMessage = responseMessage;
    
  •    this.url = url;
    
  • }
  • /**
  • \* @param message         The detail message (which is saved for later retrieval
    
  • \*                        by the {@link #getMessage()} method)
    
  • \* @param responseCode    Http response code. {@code -1} if no code can be discerned.
    
  • \* @param responseMessage Http response message
    
  • \* @param url             The url that was invoked
    
    @stephenc thanks, I am a bit stuck, net.sourceforge.findbugs:annotations is LGPL and thus incompatible with the MIT license of this project. com.github.stephenc.findbugs:findbugs-annotations is ASL2 and thus could be OK.

I'll wait for the owner of the code base to make a suggestion :-)


Reply to this email directly or view it on GitHub.

* @param cause The cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A null value is permitted,
* and indicates that the cause is nonexistent or unknown.)
* @see HttpURLConnection#getResponseCode()
* @see HttpURLConnection#getResponseMessage()
*/
public HttpException(int responseCode, String responseMessage, String url, Throwable cause) {
super("Server returned HTTP response code: " + responseCode + ", message: '" + responseMessage + "'" +
" for URL: " + url, cause);
this.responseCode = responseCode;
this.responseMessage = responseMessage;
this.url = url;
}

/**
* @param responseCode Http response code. {@code -1} if no code can be discerned.
* @param responseMessage Http response message
* @param url The url that was invoked
* @param cause The cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A null value is permitted,
* and indicates that the cause is nonexistent or unknown.)
* @see HttpURLConnection#getResponseCode()
* @see HttpURLConnection#getResponseMessage()
*/
public HttpException(int responseCode, String responseMessage, @CheckForNull URL url, Throwable cause) {
this(responseCode, responseMessage, url == null ? null : url.toString(), cause);
}

/**
* Http response code of the request that cause the exception
*
* @return {@code -1} if no code can be discerned.
*/
public int getResponseCode() {
return responseCode;
}

/**
* Http response message of the request that cause the exception
*
* @return {@code null} if no response message can be discerned.
*/
public String getResponseMessage() {
return responseMessage;
}

/**
* The http URL that caused the exception
*
* @return url
*/
public String getUrl() {
return url;
}
}
37 changes: 32 additions & 5 deletions src/main/java/org/kohsuke/github/Requester.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
Expand All @@ -64,7 +66,9 @@
*/
class Requester {
private static final List<String> METHODS_WITHOUT_BODY = asList("GET", "DELETE");


protected final transient Logger logger = Logger.getLogger(getClass().getName());

private final GitHub root;
private final List<Entry> args = new ArrayList<Entry>();
private final Map<String,String> headers = new LinkedHashMap<String, String>();
Expand Down Expand Up @@ -473,21 +477,33 @@ private void setupConnection(URL url) throws IOException {
}

private <T> T parse(Class<T> type, T instance) throws IOException {
if (uc.getResponseCode()==304)
return null; // special case handling for 304 unmodified, as the content will be ""
InputStreamReader r = null;
int responseCode = -1;
String responseMessage = null;
try {
responseCode = uc.getResponseCode();
responseMessage = uc.getResponseMessage();
if (responseCode == 304) {
return null; // special case handling for 304 unmodified, as the content will be ""
}

r = new InputStreamReader(wrapStream(uc.getInputStream()), "UTF-8");
String data = IOUtils.toString(r);
if (type!=null)
try {
return MAPPER.readValue(data,type);
} catch (JsonMappingException e) {
throw (IOException)new IOException("Failed to deserialize "+data).initCause(e);
throw (IOException)new IOException("Failed to deserialize " +data).initCause(e);
}
if (instance!=null)
return MAPPER.readerForUpdating(instance).<T>readValue(data);
return null;
} catch (FileNotFoundException e) {
// java.net.URLConnection handles 404 exception has FileNotFoundException, don't wrap exception in HttpException
// to preserve backward compatibility
throw e;
} catch (IOException e) {
throw new HttpException(responseCode, responseMessage, uc.getURL(), e);
} finally {
IOUtils.closeQuietly(r);
}
Expand All @@ -508,7 +524,18 @@ private InputStream wrapStream(InputStream in) throws IOException {
* Handle API error by either throwing it or by returning normally to retry.
*/
/*package*/ void handleApiError(IOException e) throws IOException {
if (uc.getResponseCode() == 401) // Unauthorized == bad creds
int responseCode;
try {
responseCode = uc.getResponseCode();
} catch (IOException e2) {
// likely to be a network exception (e.g. SSLHandshakeException),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Best to log at FINE just in case.

// uc.getResponseCode() and any other getter on the response will cause an exception
if (logger.isLoggable(Level.FINE))
logger.log(Level.FINE, "Silently ignore exception retrieving response code for '" + uc.getURL() + "'" +
" handling exception " + e, e);
throw e;
}
if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) // 401 / Unauthorized == bad creds
throw e;

if ("0".equals(uc.getHeaderField("X-RateLimit-Remaining"))) {
Expand Down