Skip to content

Commit be081ee

Browse files
committed
Inject responce headers in GHObject and Exceptions.
GH has specific to GET/POST headers required for analysing in case of error. Signed-off-by: Kanstantsin Shautsou <[email protected]>
1 parent 55b00a8 commit be081ee

6 files changed

Lines changed: 215 additions & 12 deletions

File tree

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@
105105
<version>4.11</version>
106106
<scope>test</scope>
107107
</dependency>
108+
<dependency>
109+
<groupId>org.hamcrest</groupId>
110+
<artifactId>hamcrest-all</artifactId>
111+
<version>1.3</version>
112+
<scope>test</scope>
113+
</dependency>
108114
<dependency>
109115
<groupId>com.fasterxml.jackson.core</groupId>
110116
<artifactId>jackson-databind</artifactId>

src/main/java/org/kohsuke/github/GHObject.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,25 @@
33
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
44
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
55
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
6-
import org.apache.commons.lang.builder.ToStringBuilder;
76
import org.apache.commons.lang.builder.ToStringStyle;
8-
import org.apache.commons.lang.reflect.FieldUtils;
97

8+
import javax.annotation.CheckForNull;
109
import java.io.IOException;
1110
import java.lang.reflect.Field;
1211
import java.net.URL;
1312
import java.util.Date;
13+
import java.util.List;
14+
import java.util.Map;
1415

1516
/**
1617
* Most (all?) domain objects in GitHub seems to have these 4 properties.
1718
*/
1819
@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
1920
"NP_UNWRITTEN_FIELD"}, justification = "JSON API")
2021
public abstract class GHObject {
22+
// not data but information related to data from responce
23+
protected Map<String, List<String>> responseHeaderFields;
24+
2125
protected String url;
2226
protected int id;
2327
protected String created_at;
@@ -26,6 +30,11 @@ public abstract class GHObject {
2630
/*package*/ GHObject() {
2731
}
2832

33+
@CheckForNull
34+
public Map<String, List<String>> getResponseHeaderFields() {
35+
return responseHeaderFields;
36+
}
37+
2938
/**
3039
* When was this resource created?
3140
*/

src/main/java/org/kohsuke/github/Requester.java

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525

2626
import com.fasterxml.jackson.databind.JsonMappingException;
2727
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
28+
import org.apache.commons.io.IOUtils;
29+
import org.kohsuke.github.exception.GHFileNotFoundException;
30+
import org.kohsuke.github.exception.GHIOException;
31+
2832
import java.io.FileNotFoundException;
2933
import java.io.IOException;
3034
import java.io.InputStream;
@@ -48,17 +52,18 @@
4852
import java.util.Locale;
4953
import java.util.Map;
5054
import java.util.NoSuchElementException;
51-
import java.util.logging.Level;
5255
import java.util.logging.Logger;
5356
import java.util.regex.Matcher;
5457
import java.util.regex.Pattern;
5558
import java.util.zip.GZIPInputStream;
59+
60+
import javax.annotation.CheckForNull;
5661
import javax.annotation.WillClose;
57-
import org.apache.commons.io.IOUtils;
5862
import org.apache.commons.lang.StringUtils;
5963

6064
import static java.util.Arrays.asList;
61-
import static java.util.logging.Level.*;
65+
import static java.util.logging.Level.FINE;
66+
import static java.util.logging.Level.FINEST;
6267
import static org.kohsuke.github.GitHub.MAPPER;
6368

6469
/**
@@ -269,7 +274,7 @@ private <T> T _to(String tailApiUrl, Class<T> type, T instance) throws IOExcepti
269274
if (nextLinkMatcher.find()) {
270275
final String link = nextLinkMatcher.group(1);
271276
T nextResult = _to(link, type, instance);
272-
277+
injectInResult(nextResult);
273278
final int resultLength = Array.getLength(result);
274279
final int nextResultLength = Array.getLength(nextResult);
275280
T concatResult = (T) Array.newInstance(type.getComponentType(), resultLength + nextResultLength);
@@ -279,6 +284,7 @@ private <T> T _to(String tailApiUrl, Class<T> type, T instance) throws IOExcepti
279284
}
280285
}
281286
}
287+
injectInResult(result);
282288
return result;
283289
} catch (IOException e) {
284290
handleApiError(e);
@@ -579,6 +585,7 @@ private void setRequestMethod(HttpURLConnection uc) throws IOException {
579585
throw new IllegalStateException("Failed to set the request method to "+method);
580586
}
581587

588+
@CheckForNull
582589
private <T> T parse(Class<T> type, T instance) throws IOException {
583590
InputStreamReader r = null;
584591
int responseCode = -1;
@@ -598,12 +605,17 @@ private <T> T parse(Class<T> type, T instance) throws IOException {
598605
String data = IOUtils.toString(r);
599606
if (type!=null)
600607
try {
601-
return MAPPER.readValue(data,type);
608+
final T readValue = MAPPER.readValue(data, type);
609+
injectInResult(readValue);
610+
return readValue;
602611
} catch (JsonMappingException e) {
603612
throw (IOException)new IOException("Failed to deserialize " +data).initCause(e);
604613
}
605-
if (instance!=null)
606-
return MAPPER.readerForUpdating(instance).<T>readValue(data);
614+
if (instance!=null) {
615+
final T readValue = MAPPER.readerForUpdating(instance).<T>readValue(data);
616+
injectInResult(readValue);
617+
return readValue;
618+
}
607619
return null;
608620
} catch (FileNotFoundException e) {
609621
// java.net.URLConnection handles 404 exception has FileNotFoundException, don't wrap exception in HttpException
@@ -616,6 +628,26 @@ private <T> T parse(Class<T> type, T instance) throws IOException {
616628
}
617629
}
618630

631+
private <T> void injectInResult(T readValue) {
632+
if (readValue instanceof GHObject[]) {
633+
for (GHObject ghObject : (GHObject[]) readValue) {
634+
injectInResult(ghObject);
635+
}
636+
} else if (readValue instanceof GHObject) {
637+
injectInResult((GHObject) readValue);
638+
}
639+
}
640+
641+
private void injectInResult(GHObject readValue) {
642+
try {
643+
final Field field = GHObject.class.getDeclaredField("responseHeaderFields");
644+
field.setAccessible(true);
645+
field.set(readValue, uc.getHeaderFields());
646+
} catch (NoSuchFieldException ignore) {
647+
} catch (IllegalAccessException ignore) {
648+
}
649+
}
650+
619651
/**
620652
* Handles the "Content-Encoding" header.
621653
*/
@@ -663,15 +695,16 @@ private InputStream wrapStream(InputStream in) throws IOException {
663695
String error = IOUtils.toString(es, "UTF-8");
664696
if (e instanceof FileNotFoundException) {
665697
// pass through 404 Not Found to allow the caller to handle it intelligently
666-
throw (IOException) new FileNotFoundException(error).initCause(e);
698+
throw (IOException) new GHFileNotFoundException(error).withResponseHeaderFields(uc).initCause(e);
667699
} else if (e instanceof HttpException) {
668700
HttpException http = (HttpException) e;
669701
throw (IOException) new HttpException(error, http.getResponseCode(), http.getResponseMessage(), http.getUrl(), e);
670702
} else {
671-
throw (IOException) new IOException(error).initCause(e);
703+
throw (IOException) new GHIOException(error).withResponceHeaderFields(uc).initCause(e);
672704
}
673-
} else
705+
} else {
674706
throw e;
707+
}
675708
} finally {
676709
IOUtils.closeQuietly(es);
677710
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package org.kohsuke.github.exception;
2+
3+
import javax.annotation.CheckForNull;
4+
import java.io.FileNotFoundException;
5+
import java.net.HttpURLConnection;
6+
import java.util.List;
7+
import java.util.Map;
8+
9+
/**
10+
* Request/responce contains useful metadata.
11+
* Custom exception allows store info for next diagnostics.
12+
*
13+
* @author Kanstantsin Shautsou
14+
*/
15+
public class GHFileNotFoundException extends FileNotFoundException {
16+
protected Map<String, List<String>> responseHeaderFields;
17+
18+
public GHFileNotFoundException() {
19+
}
20+
21+
public GHFileNotFoundException(String s) {
22+
super(s);
23+
}
24+
25+
@CheckForNull
26+
public Map<String, List<String>> getResponseHeaderFields() {
27+
return responseHeaderFields;
28+
}
29+
30+
public GHFileNotFoundException withResponseHeaderFields(HttpURLConnection urlConnection) {
31+
this.responseHeaderFields = urlConnection.getHeaderFields();
32+
return this;
33+
}
34+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package org.kohsuke.github.exception;
2+
3+
import javax.annotation.CheckForNull;
4+
import java.io.IOException;
5+
import java.net.HttpURLConnection;
6+
import java.util.List;
7+
import java.util.Map;
8+
9+
/**
10+
* Request/responce contains useful metadata.
11+
* Custom exception allows store info for next diagnostics.
12+
*
13+
* @author Kanstantsin Shautsou
14+
*/
15+
public class GHIOException extends IOException {
16+
protected Map<String, List<String>> responceHeaderFields;
17+
18+
public GHIOException() {
19+
}
20+
21+
public GHIOException(String message) {
22+
super(message);
23+
}
24+
25+
public GHIOException(String message, Throwable cause) {
26+
super(message, cause);
27+
}
28+
29+
public GHIOException(Throwable cause) {
30+
super(cause);
31+
}
32+
33+
@CheckForNull
34+
public Map<String, List<String>> getResponceHeaderFields() {
35+
return responceHeaderFields;
36+
}
37+
38+
public GHIOException withResponceHeaderFields(HttpURLConnection urlConnection) {
39+
this.responceHeaderFields = urlConnection.getHeaderFields();
40+
return this;
41+
}
42+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package org.kohsuke.github;
2+
3+
import org.apache.commons.lang.StringUtils;
4+
import org.junit.Ignore;
5+
import org.junit.Test;
6+
import org.kohsuke.github.exception.GHFileNotFoundException;
7+
8+
import java.io.IOException;
9+
import java.util.Arrays;
10+
import java.util.List;
11+
import java.util.Map;
12+
13+
import static java.util.Collections.singletonList;
14+
import static java.util.Collections.singletonMap;
15+
import static org.hamcrest.Matchers.hasItem;
16+
import static org.hamcrest.Matchers.hasKey;
17+
import static org.hamcrest.Matchers.hasValue;
18+
import static org.hamcrest.core.IsInstanceOf.instanceOf;
19+
import static org.junit.Assert.assertThat;
20+
21+
22+
/**
23+
* @author Kanstantsin Shautsou
24+
*/
25+
public class GHHookTest {
26+
27+
@Ignore
28+
@Test
29+
public void exposeResponceHeaders() throws Exception {
30+
String user1Login = "KostyaSha-auto";
31+
String user1Pass = "secret";
32+
33+
String clientId = "90140219451";
34+
String clientSecret = "1451245425";
35+
36+
String orgRepo = "KostyaSha-org/test";
37+
38+
// some login based user that has access to application
39+
final GitHub gitHub = GitHub.connectUsingPassword(user1Login, user1Pass);
40+
gitHub.getMyself();
41+
42+
// we request read
43+
final List<String> scopes = Arrays.asList("repo", "read:org", "user:email", "read:repo_hook");
44+
45+
// application creates token with scopes
46+
final GHAuthorization auth = gitHub.createOrGetAuth(clientId, clientSecret, scopes, "", "");
47+
String token = auth.getToken();
48+
if (StringUtils.isEmpty(token)) {
49+
gitHub.deleteAuth(auth.getId());
50+
token = gitHub.createOrGetAuth(clientId, clientSecret, scopes, "", "").getToken();
51+
}
52+
53+
/// now create connection using token
54+
final GitHub gitHub2 = GitHub.connectUsingOAuth(token);
55+
// some repo in organisation
56+
final GHRepository repository = gitHub2.getRepository(orgRepo);
57+
58+
// doesn't fail because we have read access
59+
final List<GHHook> hooks = repository.getHooks();
60+
61+
try {
62+
// fails because application isn't approved in organisation and you can find it only after doing real call
63+
final GHHook hook = repository.createHook(
64+
"my-hook",
65+
singletonMap("url", "http://localhost"),
66+
singletonList(GHEvent.PUSH),
67+
true
68+
);
69+
} catch (IOException ex) {
70+
assertThat(ex, instanceOf(GHFileNotFoundException.class));
71+
final GHFileNotFoundException ghFileNotFoundException = (GHFileNotFoundException) ex;
72+
final Map<String, List<String>> responseHeaderFields = ghFileNotFoundException.getResponseHeaderFields();
73+
assertThat(responseHeaderFields, hasKey("X-Accepted-OAuth-Scopes"));
74+
assertThat(responseHeaderFields.get("X-Accepted-OAuth-Scopes"),
75+
hasItem("admin:repo_hook, public_repo, repo, write:repo_hook")
76+
);
77+
}
78+
}
79+
}

0 commit comments

Comments
 (0)