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

CrumbIssuer.crumb now returns boxed version of its former self so tha… #15

Merged
merged 3 commits into from
May 9, 2018
Merged
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
41 changes: 41 additions & 0 deletions src/main/java/com/cdancy/jenkins/rest/domain/crumb/Crumb.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 com.cdancy.jenkins.rest.domain.crumb;

import java.util.List;

import org.jclouds.javax.annotation.Nullable;
import org.jclouds.json.SerializedNames;

import com.cdancy.jenkins.rest.domain.common.Error;
import com.cdancy.jenkins.rest.domain.common.ErrorsHolder;
import com.cdancy.jenkins.rest.domain.common.Value;
import com.cdancy.jenkins.rest.JenkinsUtils;
import com.google.auto.value.AutoValue;

@AutoValue
public abstract class Crumb implements Value<String>, ErrorsHolder {

@SerializedNames({ "value", "errors" })
public static Crumb create(@Nullable final String value,
final List<Error> errors) {

return new AutoValue_Crumb(value,
JenkinsUtils.nullToEmpty(errors));
}
}
Original file line number Diff line number Diff line change
@@ -22,20 +22,17 @@

import static org.jclouds.http.HttpUtils.returnValueOnCodeOrNull;

import com.cdancy.jenkins.rest.JenkinsUtils;
import com.cdancy.jenkins.rest.domain.common.RequestStatus;
import com.cdancy.jenkins.rest.domain.common.Error;
import com.cdancy.jenkins.rest.domain.crumb.Crumb;
import com.cdancy.jenkins.rest.domain.system.SystemInfo;

import com.google.common.collect.Lists;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import java.util.Iterator;

import java.util.List;

import org.jclouds.Fallback;
import org.jclouds.Fallbacks;
import org.jclouds.rest.ResourceNotFoundException;

public final class JenkinsFallbacks {
@@ -74,12 +71,23 @@ public static final class RequestStatusOnError implements Fallback<Object> {
public Object createOrPropagate(final Throwable throwable) throws Exception {
if (checkNotNull(throwable, "throwable") != null) {
try {
return createRequestStatusFromErrors(getErrors(throwable));
return RequestStatus.create(false, getErrors(throwable));
} catch (JsonSyntaxException e) {
final Error error = Error.create(null, throwable.getMessage(),
throwable.getClass().getName());
final List<Error> errors = Lists.newArrayList(error);
return RequestStatus.create(false, errors);
return RequestStatus.create(false, getErrors(e));
}
}
throw propagate(throwable);
}
}

public static final class CrumbOnError implements Fallback<Object> {
@Override
public Object createOrPropagate(final Throwable throwable) throws Exception {
if (checkNotNull(throwable, "throwable") != null) {
try {
return Crumb.create(null, getErrors(throwable));
} catch (JsonSyntaxException e) {
return Crumb.create(null, getErrors(e));
}
}
throw propagate(throwable);
@@ -94,13 +102,11 @@ public Object createOrPropagate(final Throwable throwable) throws Exception {
try {
if (throwable.getClass() == ResourceNotFoundException.class) {
return RequestStatus.create(true, null);
} else {
return RequestStatus.create(false, getErrors(throwable));
}
return createRequestStatusFromErrors(getErrors(throwable));
} catch (JsonSyntaxException e) {
final Error error = Error.create(null, throwable.getMessage(),
throwable.getClass().getName());
final List<Error> errors = Lists.newArrayList(error);
return RequestStatus.create(false, errors);
return RequestStatus.create(false, getErrors(e));
}
}
throw propagate(throwable);
@@ -113,8 +119,16 @@ public static SystemInfo createSystemInfoFromErrors(final List<Error> errors) {
illegalValue, illegalValue, illegalValue, errors);
}

public static RequestStatus createRequestStatusFromErrors(final List<Error> errors) {
return RequestStatus.create(false, errors);
/**
* Parse list of Error's from generic Exception.
*
* @param output Exception containing error data
* @return List of culled Error's
*/
public static List<Error> getErrors(final Exception output) {
final Error error = Error.create(null, output.getMessage(),
output.getClass().getName());
return Lists.newArrayList(error);
}

/**
Original file line number Diff line number Diff line change
@@ -23,18 +23,25 @@
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;

import org.jclouds.rest.annotations.Fallback;
import org.jclouds.rest.annotations.ResponseParser;
import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.rest.annotations.QueryParams;

import com.cdancy.jenkins.rest.domain.crumb.Crumb;
import com.cdancy.jenkins.rest.fallbacks.JenkinsFallbacks;
import com.cdancy.jenkins.rest.filters.JenkinsNoCrumbAuthenticationFilter;
import com.cdancy.jenkins.rest.parsers.CrumbParser;

@RequestFilters(JenkinsNoCrumbAuthenticationFilter.class)
@Path("/crumbIssuer/api/xml")
public interface CrumbIssuerApi {

@Named("crumb-issuer:crumb")
@Fallback(JenkinsFallbacks.CrumbOnError.class)
@ResponseParser(CrumbParser.class)
@QueryParams(keys = { "xpath" }, values = { "concat(//crumbRequestField,\":\",//crumb)" })
@Consumes(MediaType.TEXT_PLAIN)
@GET
String crumb();
Crumb crumb();
}
Original file line number Diff line number Diff line change
@@ -23,18 +23,23 @@
import com.cdancy.jenkins.rest.JenkinsApi;
import com.cdancy.jenkins.rest.JenkinsAuthentication;
import com.cdancy.jenkins.rest.auth.AuthenticationType;
import com.cdancy.jenkins.rest.domain.crumb.Crumb;

import org.jclouds.http.HttpException;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpRequestFilter;

import com.google.common.net.HttpHeaders;

import org.jclouds.rest.ResourceNotFoundException;

@Singleton
public class JenkinsAuthenticationFilter implements HttpRequestFilter {
private final JenkinsAuthentication creds;
private final JenkinsApi jenkinsApi;
private static volatile String crumbValue = null; // can be shared across requests

// key = Crumb, value = true if exception is ResourceNotFoundException false otherwise
private static volatile Pair<Crumb, Boolean> crumbPair = null;
private static final String CRUMB_HEADER = "Jenkins-Crumb";

@Inject
@@ -49,23 +54,53 @@ public HttpRequest filter(final HttpRequest request) throws HttpException {
return request;
} else {
final String authHeader = creds.authType() + " " + creds.authValue();
return request.toBuilder()
.addHeader(HttpHeaders.AUTHORIZATION, authHeader)
.addHeader(CRUMB_HEADER, getCrumbValue())
.build();
final HttpRequest.Builder<? extends HttpRequest.Builder<?>> builder = request.toBuilder();
builder.addHeader(HttpHeaders.AUTHORIZATION, authHeader);

// whether to add crumb header or not
final Pair<Crumb, Boolean> localCrumb = getCrumb();
if (localCrumb.getKey().value() != null) {
builder.addHeader(CRUMB_HEADER, localCrumb.getKey().value());
} else {
if (localCrumb.getValue() == false) {
throw new RuntimeException("Unexpected exception being thrown: error=" + localCrumb.getKey().errors().get(0));
}
}

return builder.build();
}
}

private String getCrumbValue() {
String crumbValueInit = JenkinsAuthenticationFilter.crumbValue;
private Pair<Crumb, Boolean> getCrumb() {
Pair<Crumb, Boolean> crumbValueInit = JenkinsAuthenticationFilter.crumbPair;
if (crumbValueInit == null) {
synchronized(this) {
crumbValueInit = JenkinsAuthenticationFilter.crumbValue;
crumbValueInit = JenkinsAuthenticationFilter.crumbPair;
if (crumbValueInit == null) {
JenkinsAuthenticationFilter.crumbValue = crumbValueInit = jenkinsApi.crumbIssuerApi().crumb().split(":")[1];
final Crumb crumb = jenkinsApi.crumbIssuerApi().crumb();
final Boolean isRNFE = crumb.errors().isEmpty()
? true
: crumb.errors().get(0).exceptionName().endsWith(ResourceNotFoundException.class.getSimpleName());
JenkinsAuthenticationFilter.crumbPair = crumbValueInit = new Pair(crumb, isRNFE);
}
}
}
return crumbValueInit;
}

// simple impl/copy of javafx.util.Pair
private class Pair<A, B> {
private final A a;
private final B b;
public Pair(final A a, final B b) {
this.a = a;
this.b = b;
}
public A getKey() {
return a;
}
public B getValue() {
return b;
}
}
}
Original file line number Diff line number Diff line change
@@ -30,7 +30,6 @@
import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpResponseException;
import org.jclouds.logging.Logger;
import org.jclouds.rest.ResourceAlreadyExistsException;
import org.jclouds.rest.ResourceNotFoundException;
import org.jclouds.rest.AuthorizationException;
@@ -42,8 +41,6 @@
* Handle errors and propagate exception
*/
public class JenkinsErrorHandler implements HttpErrorHandler {
@Resource
protected Logger logger = Logger.NULL;

@Override
public void handleError(final HttpCommand command, final HttpResponse response) {
50 changes: 50 additions & 0 deletions src/main/java/com/cdancy/jenkins/rest/parsers/CrumbParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 com.cdancy.jenkins.rest.parsers;

import com.cdancy.jenkins.rest.domain.crumb.Crumb;

import com.google.common.base.Function;
import java.io.IOException;

import javax.inject.Singleton;

import org.jclouds.http.HttpResponse;
import org.jclouds.util.Strings2;

/**
* Turn a valid response, but one that has no body, into a Crumb.
*/
@Singleton
public class CrumbParser implements Function<HttpResponse, Crumb> {

@Override
public Crumb apply(final HttpResponse input) {
final int statusCode = input.getStatusCode();
if (statusCode >= 200 && statusCode < 400) {
try {
final String response = Strings2.toStringAndClose(input.getPayload().openStream());
return Crumb.create(response.split(":")[1], null);
} catch (final IOException e) {
throw new RuntimeException(input.getStatusLine(), e);
}
} else {
throw new RuntimeException(input.getStatusLine());
}
}
}
Original file line number Diff line number Diff line change
@@ -17,18 +17,22 @@
package com.cdancy.jenkins.rest.features;

import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;

import org.testng.annotations.Test;

import com.cdancy.jenkins.rest.BaseJenkinsApiLiveTest;
import com.cdancy.jenkins.rest.domain.crumb.Crumb;

@Test(groups = "live", testName = "CrumbIssuerApiLiveTest", singleThreaded = true)
public class CrumbIssuerApiLiveTest extends BaseJenkinsApiLiveTest {

@Test
public void testGetCrumb() {
final String crumb = api().crumb();
final Crumb crumb = api().crumb();
assertNotNull(crumb);
assertNotNull(crumb.value());
assertTrue(crumb.errors().isEmpty());
}

private CrumbIssuerApi api() {
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@

import com.cdancy.jenkins.rest.JenkinsApi;
import com.cdancy.jenkins.rest.BaseJenkinsMockTest;
import com.cdancy.jenkins.rest.domain.crumb.Crumb;

import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.MockWebServer;
@@ -38,14 +39,14 @@ public class CrumbIssuerApiMockTest extends BaseJenkinsMockTest {
public void testGetSystemInfo() throws Exception {
MockWebServer server = mockWebServer();

final String body = "";
server.enqueue(new MockResponse().setBody("Jenkins-Crumb:04a1109fc2db171362c966ebe9fc87f0").setResponseCode(200));
final String value = "04a1109fc2db171362c966ebe9fc87f0";
server.enqueue(new MockResponse().setBody("Jenkins-Crumb:" + value).setResponseCode(200));
JenkinsApi jenkinsApi = api(server.getUrl("/"));
CrumbIssuerApi api = jenkinsApi.crumbIssuerApi();
try {
final String instance = api.crumb();
final Crumb instance = api.crumb();
assertNotNull(instance);
assertTrue(instance.contains(":"));
assertTrue(instance.value().equals(value));
assertSentAccept(server, "GET", "/crumbIssuer/api/xml?xpath=concat%28//crumbRequestField,%22%3A%22,//crumb%29", MediaType.TEXT_PLAIN);
} finally {
jenkinsApi.close();