UX-12# blueocean-rest implementation
- Embedded driver uses blueocean-rest module to expose service api implementation via REST - Refactored core to move out embryo in to war module - BlueOceanRootAction moved in to embedded driver - README added to blueocean-rest and embedded-driver modules
This commit is contained in:
parent
a8b58fc8bb
commit
f39cf0b5b4
|
@ -1,33 +1,54 @@
|
|||
package io.jenkins.blueocean.commons;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.core.JsonParseException;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
/**
|
||||
* @author Vivek Pandey
|
||||
**/
|
||||
public class JsonConverter{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(JsonConverter.class);
|
||||
public static final String DATE_FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
|
||||
public static final ObjectMapper om = createObjectMapper();
|
||||
|
||||
public static <T> T toJava(String data, Class<T> type) {
|
||||
|
||||
try {
|
||||
return om.readValue(data, type);
|
||||
return toJava(new ByteArrayInputStream(data.getBytes("UTF-8")), type);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(String.format("Invalid json. Failed to convert json %s to type %s", data, type));
|
||||
String msg = String.format("Failed to convert %s to type %s", data, type);
|
||||
LOGGER.error(msg, e);
|
||||
throw new ServiceException.UnexpectedErrorExpcetion(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T toJava(InputStream data, Class<T> type) {
|
||||
try {
|
||||
return om.readValue(data, type);
|
||||
} catch (JsonParseException e){
|
||||
String msg = "Json parsing failure: "+e.getMessage();
|
||||
LOGGER.error(msg, e);
|
||||
throw new ServiceException.BadRequestExpception("Json parsing failure: "+e.getMessage());
|
||||
} catch (JsonMappingException e){
|
||||
String msg = String.format("Failed to map Json to java type : %s. %s ", type, e.getMessage());
|
||||
LOGGER.error(msg, e);
|
||||
throw new ServiceException.UnexpectedErrorExpcetion(msg);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(String.format("Invalid json. Failed to convert %s to type %s", data, type));
|
||||
String msg = String.format("Failed to convert %s to type %s", data, type);
|
||||
LOGGER.error(msg, e);
|
||||
throw new ServiceException.UnexpectedErrorExpcetion(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,10 +62,11 @@ public class JsonConverter{
|
|||
|
||||
private static ObjectMapper createObjectMapper(){
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setDateFormat(new SimpleDateFormat(DATE_FORMAT_STRING));
|
||||
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
|
||||
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
|
||||
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
|
||||
return mapper;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ package io.jenkins.blueocean.commons;
|
|||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import edu.umd.cs.findbugs.annotations.NonNull;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
@ -66,7 +66,7 @@ public class ServiceException extends RuntimeException{
|
|||
|
||||
public final int code;
|
||||
|
||||
public ErrorMessage(@NonNull Integer code, @NonNull String message) {
|
||||
public ErrorMessage(@Nonnull Integer code, @Nonnull String message) {
|
||||
this.code=code;
|
||||
this.message = message;
|
||||
}
|
||||
|
@ -264,11 +264,48 @@ public class ServiceException extends RuntimeException{
|
|||
}
|
||||
}
|
||||
|
||||
public static final int BAD_REQUEST = 401;
|
||||
|
||||
public static class UnsupportedMediaTypeException extends ServiceException{
|
||||
|
||||
public UnsupportedMediaTypeException(String message) {
|
||||
super(UNSUPPORTED_MEDIA_TYPE, message);
|
||||
}
|
||||
|
||||
public UnsupportedMediaTypeException(String message, Throwable throwable ) {
|
||||
super(UNSUPPORTED_MEDIA_TYPE, message, throwable);
|
||||
}
|
||||
|
||||
public UnsupportedMediaTypeException(ErrorMessage errorMessage) {
|
||||
super(UNSUPPORTED_MEDIA_TYPE, errorMessage.message);
|
||||
}
|
||||
public UnsupportedMediaTypeException(ErrorMessage errorMessage, Throwable throwable ) {
|
||||
super(UNSUPPORTED_MEDIA_TYPE, errorMessage.message, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
public static class MethodNotAllowedException extends ServiceException{
|
||||
|
||||
public MethodNotAllowedException(String message) {
|
||||
super(METHOD_NOT_ALLOWED, message);
|
||||
}
|
||||
|
||||
public MethodNotAllowedException(String message, Throwable throwable ) {
|
||||
super(METHOD_NOT_ALLOWED, message, throwable);
|
||||
}
|
||||
|
||||
public MethodNotAllowedException(ErrorMessage errorMessage) {
|
||||
super(METHOD_NOT_ALLOWED, errorMessage.message);
|
||||
}
|
||||
public MethodNotAllowedException(ErrorMessage errorMessage, Throwable throwable ) {
|
||||
super(METHOD_NOT_ALLOWED, errorMessage.message, throwable);
|
||||
}
|
||||
}
|
||||
public static final int BAD_REQUEST = 400;
|
||||
public static final int UNAUTHORIZED = 401;
|
||||
public static final int FORBIDDEN = 403;
|
||||
public static final int NOT_FOUND = 404;
|
||||
public static final int METHOD_NOT_ALLOWED = 405;
|
||||
public static final int UNSUPPORTED_MEDIA_TYPE = 415;
|
||||
public static final int CONFLICT = 409;
|
||||
public static final int UNPROCESSABLE_ENTITY = 422;
|
||||
public static final int TOO_MANY_REQUESTS = 429;
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package io.jenkins.blueocean.commons.guice;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.matcher.Matchers;
|
||||
import hudson.Extension;
|
||||
import io.jenkins.blueocean.commons.JsonConverter;
|
||||
|
||||
/**
|
||||
* Commons Guice module
|
||||
*
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
@Extension
|
||||
public class CommonsModule extends AbstractModule {
|
||||
@Override
|
||||
protected void configure() {
|
||||
bindListener(Matchers.any(), new Slf4jTypeListener());
|
||||
bind(JsonConverter.class);
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package io.jenkins.blueocean.commons.guice;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface InjectLogger {
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package io.jenkins.blueocean.commons.guice;
|
||||
|
||||
import com.google.inject.MembersInjector;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
public class Slf4jMembersInjector<T> implements MembersInjector<T> {
|
||||
private final Field field;
|
||||
private final Logger logger;
|
||||
|
||||
Slf4jMembersInjector(Field aField) {
|
||||
field = aField;
|
||||
logger = LoggerFactory.getLogger(field.getDeclaringClass());
|
||||
field.setAccessible(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectMembers(T t) {
|
||||
try {
|
||||
field.set(t, logger);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package io.jenkins.blueocean.commons.guice;
|
||||
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.spi.TypeEncounter;
|
||||
import com.google.inject.spi.TypeListener;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
|
||||
/**
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
public class Slf4jTypeListener implements TypeListener{
|
||||
@Override
|
||||
public <I> void hear(TypeLiteral<I> iTypeLiteral, TypeEncounter<I> iTypeEncounter) {
|
||||
for (Field field : iTypeLiteral.getRawType().getDeclaredFields()) {
|
||||
if (field.getType() == Logger.class
|
||||
&& field.isAnnotationPresent(InjectLogger.class)) {
|
||||
iTypeEncounter.register(new Slf4jMembersInjector<I>(field));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
# Usage
|
||||
|
||||
## Run embedded-driver
|
||||
|
||||
cd embedded-driver
|
||||
mvn hpi:run
|
||||
|
||||
This will launch jenkins with BO plugin.
|
||||
|
||||
BlueOcean UI is available at:
|
||||
|
||||
http://localhost:8080/jenkins/bo
|
||||
|
||||
|
||||
BlueOcean rest API base URL is:
|
||||
|
||||
http://localhost:8080/jenkins/bo/rest
|
||||
|
||||
|
||||
## Get a user
|
||||
|
||||
curl -v -X GET http://localhost:8080/jenkins/bo/rest/users/alice
|
||||
|
||||
{
|
||||
"user" : {
|
||||
"id" : "alice",
|
||||
"name" : "Alice"
|
||||
}
|
||||
}
|
||||
|
||||
## Get user details
|
||||
|
||||
curl -v -X GET http://localhost:8080/jenkins/bo/rest/users/alice\?details\=true
|
||||
|
||||
{
|
||||
"userDetails" : {
|
||||
"id" : "alice",
|
||||
"name" : "Alice",
|
||||
"email" : "none"
|
||||
}
|
||||
}
|
||||
|
||||
> Backend needs to send email of user if present
|
||||
|
||||
## Find users in an organization
|
||||
|
||||
curl -v -X GET http://localhost:8080/jenkins/bo/rest/search\?q\=type:user\;organization:jenkins
|
||||
|
||||
{
|
||||
"users" : [ {
|
||||
"id" : "alice",
|
||||
"name" : "Alice"
|
||||
} ]
|
||||
}
|
||||
|
||||
## Get organization details
|
||||
|
||||
curl -v -X GET http://localhost:8080/jenkins/bo/rest/organizations/jenkins
|
||||
|
||||
{
|
||||
"organization" : {
|
||||
"name" : "jenkins"
|
||||
}
|
||||
}
|
||||
|
||||
## Get a Pipeline
|
||||
|
||||
curl -v -X GET "http://localhost:8080/jenkins/bo/rest/organizations/jenkins/pipelines/test1"
|
||||
|
||||
{
|
||||
"pipeline" : {
|
||||
"organization" : "jenkins",
|
||||
"name" : "test1",
|
||||
"branches" : [ ]
|
||||
}
|
||||
}
|
||||
|
||||
## Get Pipelines
|
||||
|
||||
curl -v -X GET http://localhost:8080/jenkins/bo/rest/organizations/jenkins/pipelines
|
||||
|
||||
{
|
||||
"pipelines" : [ {
|
||||
"organization" : "jenkins",
|
||||
"name" : "test1",
|
||||
"branches" : [ ]
|
||||
} ]
|
||||
}
|
||||
|
||||
## Get all runs in a pipeline
|
||||
|
||||
curl -v -X GET http://localhost:8080/jenkins/bo/rest/organizations/jenkins/pipelines/test1/runs
|
||||
|
||||
{
|
||||
"runs" : [ {
|
||||
"id" : "2",
|
||||
"pipeline" : "test1",
|
||||
"organization" : "jenkins",
|
||||
"status" : "SUCCESSFUL",
|
||||
"startTime" : "2016-02-19T11:14:53.074Z",
|
||||
"enQueueTime" : "2016-02-19T11:14:53.072Z",
|
||||
"endTime" : "2016-02-19T11:14:53.109Z",
|
||||
"durationInMillis" : 35,
|
||||
"runSummary" : "stable",
|
||||
"result" : {
|
||||
"type" : "FreeStyleBuild",
|
||||
"data" : { }
|
||||
}
|
||||
}, {
|
||||
"id" : "1",
|
||||
"pipeline" : "test1",
|
||||
"organization" : "jenkins",
|
||||
"status" : "SUCCESSFUL",
|
||||
"startTime" : "2016-02-18T19:39:36.679Z",
|
||||
"enQueueTime" : "2016-02-18T19:39:36.674Z",
|
||||
"endTime" : "2016-02-18T19:39:36.747Z",
|
||||
"durationInMillis" : 68,
|
||||
"runSummary" : "stable",
|
||||
"result" : {
|
||||
"type" : "FreeStyleBuild",
|
||||
"data" : { }
|
||||
}
|
||||
} ]
|
||||
}
|
||||
|
||||
|
||||
## Get latest run of a pipeline
|
||||
|
||||
curl -v -X GET http://localhost:8080/jenkins/bo/rest/organizations/jenkins/pipelines/test1/runs\?latestOnly\=true
|
||||
|
||||
{
|
||||
"runs" : [ {
|
||||
"id" : "2",
|
||||
"pipeline" : "test1",
|
||||
"organization" : "jenkins",
|
||||
"status" : "SUCCESSFUL",
|
||||
"startTime" : "2016-02-19T11:14:53.074Z",
|
||||
"enQueueTime" : "2016-02-19T11:14:53.072Z",
|
||||
"endTime" : "2016-02-19T11:14:53.109Z",
|
||||
"durationInMillis" : 35,
|
||||
"runSummary" : "stable",
|
||||
"result" : {
|
||||
"type" : "FreeStyleBuild",
|
||||
"data" : { }
|
||||
}
|
||||
} ]
|
||||
}
|
||||
|
||||
## Get a run details
|
||||
|
||||
curl -v -X GET http://localhost:8080/jenkins/bo/rest/organizations/jenkins/pipelines/test2/runs/1
|
||||
|
||||
{
|
||||
"run" : {
|
||||
"id" : "1",
|
||||
"pipeline" : "test2",
|
||||
"organization" : "jenkins",
|
||||
"status" : "SUCCESSFUL",
|
||||
"startTime" : "2016-02-19T11:14:41.414Z",
|
||||
"enQueueTime" : "2016-02-19T11:14:41.411Z",
|
||||
"endTime" : "2016-02-19T11:14:41.482Z",
|
||||
"durationInMillis" : 68,
|
||||
"runSummary" : "stable",
|
||||
"result" : {
|
||||
"type" : "FreeStyleBuild",
|
||||
"data" : { }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>io.jenkins.blueocean</groupId>
|
||||
<artifactId>blueocean-parent</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>blueocean-rest</artifactId>
|
||||
<packaging>hpi</packaging>
|
||||
|
||||
<name>BlueOcean :: Alpha Plugin</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>blueocean-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>blueocean-commons</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>blueocean-security-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>profile-service-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>pipeline-service-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,222 @@
|
|||
package io.jenkins.blueocean.rest;
|
||||
|
||||
import hudson.Extension;
|
||||
import hudson.ExtensionList;
|
||||
import io.jenkins.blueocean.api.pipeline.FindPipelineRunsRequest;
|
||||
import io.jenkins.blueocean.api.pipeline.FindPipelinesRequest;
|
||||
import io.jenkins.blueocean.api.pipeline.GetPipelineRequest;
|
||||
import io.jenkins.blueocean.api.pipeline.GetPipelineRunRequest;
|
||||
import io.jenkins.blueocean.api.pipeline.PipelineService;
|
||||
import io.jenkins.blueocean.api.profile.CreateOrganizationRequest;
|
||||
import io.jenkins.blueocean.api.profile.CreateOrganizationResponse;
|
||||
import io.jenkins.blueocean.api.profile.FindUsersRequest;
|
||||
import io.jenkins.blueocean.api.profile.GetOrganizationRequest;
|
||||
import io.jenkins.blueocean.api.profile.GetUserDetailsRequest;
|
||||
import io.jenkins.blueocean.api.profile.GetUserRequest;
|
||||
import io.jenkins.blueocean.api.profile.ProfileService;
|
||||
import io.jenkins.blueocean.commons.ServiceException;
|
||||
import io.jenkins.blueocean.commons.guice.InjectLogger;
|
||||
import io.jenkins.blueocean.rest.router.Route;
|
||||
import io.jenkins.blueocean.rest.router.RouteContext;
|
||||
import io.jenkins.blueocean.rest.router.Router;
|
||||
import jenkins.model.Jenkins;
|
||||
import org.kohsuke.stapler.HttpResponse;
|
||||
import org.kohsuke.stapler.StaplerRequest;
|
||||
import org.kohsuke.stapler.StaplerResponse;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Entrypoint for blueocean REST apis. $CONTEXT_PATH/rest being root. e.g. /jenkins/rest
|
||||
*
|
||||
*
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
@Extension
|
||||
public final class ApiHead{
|
||||
|
||||
@InjectLogger
|
||||
private Logger logger;
|
||||
|
||||
private final ProfileService profileService;
|
||||
|
||||
private final PipelineService pipelineService;
|
||||
|
||||
private static final String USER_ID_PARAM=":user-id";
|
||||
private static final String ORGANIZATION_ID_PARAM=":organization-id";
|
||||
private static final String PIPELINE_ID_PARAM=":pipeline-id";
|
||||
private static final String RUN_ID_PARAM=":run-id";
|
||||
|
||||
private static final String ACCEPT_TYPE_REQUEST_MIME_HEADER = "Accept";
|
||||
|
||||
|
||||
public ApiHead() {
|
||||
this.profileService = getService(ProfileService.class);
|
||||
this.pipelineService = getService(PipelineService.class);
|
||||
|
||||
|
||||
Router.get(new Route.RouteImpl(String.format("users/%s",USER_ID_PARAM)) {
|
||||
@Override
|
||||
public Object handle(Request request, Response response) {
|
||||
response.status(200);
|
||||
Boolean details = request.queryParam("details", Boolean.class);
|
||||
|
||||
if(details != null && details) {
|
||||
return profileService.getUserDetails(request.principal(),
|
||||
new GetUserDetailsRequest(request.pathParam(USER_ID_PARAM)));
|
||||
}else {
|
||||
return profileService.getUser(request.principal(),
|
||||
new GetUserRequest(request.pathParam(USER_ID_PARAM)));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Router.get(new Route.RouteImpl(String.format("users/%s",ORGANIZATION_ID_PARAM)) {
|
||||
@Override
|
||||
public Object handle(Request request, Response response) {
|
||||
response.status(200);
|
||||
return profileService.getOrganization(request.principal(),
|
||||
new GetOrganizationRequest(request.pathParam(ORGANIZATION_ID_PARAM)));
|
||||
}
|
||||
});
|
||||
|
||||
Router.get(new Route.RouteImpl(String.format("organizations/%s",ORGANIZATION_ID_PARAM)) {
|
||||
@Override
|
||||
public Object handle(Request request, Response response) {
|
||||
response.status(200);
|
||||
|
||||
return profileService.getOrganization(request.principal(),
|
||||
new GetOrganizationRequest(request.pathParam(ORGANIZATION_ID_PARAM)));
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
Router.post(new Route.RouteImpl("organizations") {
|
||||
@Override
|
||||
public Object handle(Request request, Response response) {
|
||||
CreateOrganizationResponse createOrganizationResponse = profileService.createOrganization(request.principal(),
|
||||
new CreateOrganizationRequest(request.pathParam(ORGANIZATION_ID_PARAM)));
|
||||
response.status(201);
|
||||
response.header("Location", appendSlashToPath(request.uri())+createOrganizationResponse.organization);
|
||||
return createOrganizationResponse;
|
||||
}
|
||||
});
|
||||
|
||||
Router.get(new Route.RouteImpl(String.format("organizations/%s/pipelines", ORGANIZATION_ID_PARAM)){
|
||||
@Override
|
||||
public Object handle(Request request, Response response) {
|
||||
response.status(200);
|
||||
return pipelineService.findPipelines(request.principal(),
|
||||
new FindPipelinesRequest(request.pathParam(ORGANIZATION_ID_PARAM), null));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Router.get(new Route.RouteImpl(String.format("organizations/%s/pipelines/%s",
|
||||
ORGANIZATION_ID_PARAM, PIPELINE_ID_PARAM)){
|
||||
@Override
|
||||
public Object handle(Request request, Response response) {
|
||||
response.status(200);
|
||||
return pipelineService.getPipeline(request.principal(),
|
||||
new GetPipelineRequest(request.pathParam(ORGANIZATION_ID_PARAM),
|
||||
request.pathParam(PIPELINE_ID_PARAM)));
|
||||
}
|
||||
});
|
||||
|
||||
Router.get(new Route.RouteImpl(String.format("organizations/%s/pipelines/%s/runs",
|
||||
ORGANIZATION_ID_PARAM, PIPELINE_ID_PARAM)){
|
||||
@Override
|
||||
public Object handle(Request request, Response response) {
|
||||
response.status(200);
|
||||
|
||||
return pipelineService.findPipelineRuns(request.principal(),
|
||||
new FindPipelineRunsRequest(request.pathParam(ORGANIZATION_ID_PARAM),
|
||||
request.pathParam(PIPELINE_ID_PARAM),
|
||||
request.queryParam("latestOnly", Boolean.class),null,null,null));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Router.get(new Route.RouteImpl(String.format("organizations/%s/pipelines/%s/runs/%s",
|
||||
ORGANIZATION_ID_PARAM, PIPELINE_ID_PARAM, RUN_ID_PARAM)){
|
||||
@Override
|
||||
public Object handle(Request request, Response response) {
|
||||
response.status(200);
|
||||
|
||||
return pipelineService.getPipelineRun(request.principal(),
|
||||
new GetPipelineRunRequest(request.pathParam(ORGANIZATION_ID_PARAM),
|
||||
request.pathParam(PIPELINE_ID_PARAM), request.pathParam(RUN_ID_PARAM)));
|
||||
}
|
||||
});
|
||||
Router.get(new Route.RouteImpl("search"){
|
||||
@Override
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Object handle(Request request, Response response) {
|
||||
response.status(200);
|
||||
|
||||
Query query = Query.parse(request.queryParam("q", true));
|
||||
assert query != null; //will never be the case since its taken care in request.queryParam()
|
||||
switch (query.type){
|
||||
case "user":
|
||||
return profileService.findUsers(request.principal(),
|
||||
new FindUsersRequest(query.param("organization", true),
|
||||
query.param("start", Long.class),
|
||||
query.param("limit", Long.class)));
|
||||
case "pipeline":
|
||||
return pipelineService.findPipelines(request.principal(),
|
||||
new FindPipelinesRequest(query.param("organization", true), query.param("pipeline")));
|
||||
case "run":
|
||||
return pipelineService.findPipelineRuns(request.principal(),
|
||||
new FindPipelineRunsRequest(query.param("organization", true),
|
||||
query.param("pipeline"),
|
||||
query.param("latestOnly", Boolean.class),
|
||||
query.param("branches", List.class),
|
||||
query.param("start", Long.class),
|
||||
query.param("limit", Long.class)));
|
||||
default:
|
||||
throw new ServiceException.BadRequestExpception("Unknown query type: "+query.type);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public HttpResponse doDynamic(StaplerRequest request, StaplerResponse response){
|
||||
String method = request.getMethod().toLowerCase();
|
||||
String url = request.getOriginalRestOfPath();
|
||||
String acceptType = request.getHeader(ACCEPT_TYPE_REQUEST_MIME_HEADER);
|
||||
Body body = new Body();
|
||||
HttpMethod httpMethod = HttpMethod.get(method);
|
||||
|
||||
RouteContext context = RouteContext.create().withAcceptType(acceptType).withBody(body)
|
||||
.withHttpMethod(httpMethod).withMatcher(Router.INSTANCE.routeMatcher())
|
||||
.withResponse(new Response(response)).withUri(url).withHttpRequest(request);
|
||||
|
||||
try{
|
||||
Router.execute(context);
|
||||
return JsonHttpResponse.json(context.body().get());
|
||||
}catch (ServiceException e){
|
||||
if(e.status > 499) {
|
||||
logger.error(e.getMessage(), e);
|
||||
}
|
||||
return JsonHttpResponse.json(e.errorMessage, e.status);
|
||||
}catch (Exception e){
|
||||
logger.error(e.getMessage(), e);
|
||||
return JsonHttpResponse.json(new ServiceException.ErrorMessage(500, "Unexpected error"), 500);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T getService(Class<T> type){
|
||||
ExtensionList<T> services = Jenkins.getActiveInstance().getExtensionList(type);
|
||||
if(services.isEmpty()){
|
||||
throw new ServiceException.UnexpectedErrorExpcetion("No implementation found for service API: " + type);
|
||||
}
|
||||
return services.get(0);
|
||||
}
|
||||
|
||||
private String appendSlashToPath(String path){
|
||||
return path.endsWith("/") ? path : path + "/";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package io.jenkins.blueocean.rest;
|
||||
|
||||
/**
|
||||
* Wrapper over http body content
|
||||
*
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
public class Body {
|
||||
|
||||
private Object content;
|
||||
public Body() {
|
||||
}
|
||||
|
||||
public boolean isSet(){
|
||||
return content != null;
|
||||
}
|
||||
|
||||
public Object get(){
|
||||
return content;
|
||||
}
|
||||
|
||||
public void set(Object content){
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public boolean notSet() {
|
||||
return content == null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package io.jenkins.blueocean.rest;
|
||||
|
||||
import io.jenkins.blueocean.rest.router.Route;
|
||||
|
||||
/**
|
||||
* Filter is just another Route
|
||||
*
|
||||
* @author Per Wendel
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
public interface Filter extends Route{
|
||||
|
||||
abstract class FilterImpl implements Filter{
|
||||
private final String path;
|
||||
private final String acceptType;
|
||||
private final String contentType;
|
||||
|
||||
public FilterImpl(String path) {
|
||||
this.path = path;
|
||||
this.acceptType = Route.DEFAULT_ACCEPT_TYPE;
|
||||
this.contentType = Route.DEFAULT_CONTENT_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String path() {
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String accessType() {
|
||||
return acceptType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String contentType() {
|
||||
return contentType;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package io.jenkins.blueocean.rest;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* @author Per Wendel
|
||||
*/
|
||||
public enum HttpMethod {
|
||||
get, post, put, patch, delete, head, trace, connect, options, before, after, unsupported;
|
||||
|
||||
private static HashMap<String, HttpMethod> methods = new HashMap();
|
||||
|
||||
static {
|
||||
for (HttpMethod method : values()) {
|
||||
methods.put(method.toString(), method);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the HttpMethod corresponding to the provided string. If no corresponding method can be found
|
||||
* {@link HttpMethod#unsupported} will be returned.
|
||||
*/
|
||||
public static HttpMethod get(String methodStr) {
|
||||
HttpMethod method = methods.get(methodStr);
|
||||
return method != null ? method : unsupported;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package io.jenkins.blueocean.rest;
|
||||
|
||||
import io.jenkins.blueocean.commons.JsonConverter;
|
||||
import org.kohsuke.stapler.HttpResponse;
|
||||
import org.kohsuke.stapler.StaplerRequest;
|
||||
import org.kohsuke.stapler.StaplerResponse;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author Vivek Pandey
|
||||
**/
|
||||
public class JsonHttpResponse{
|
||||
public static final String MEDIA_TYPE_APPLICATION_JSON = "application/json;charset=UTF-8";
|
||||
|
||||
public static HttpResponse json(final Object value, final int status) {
|
||||
return new HttpResponse() {
|
||||
public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node) throws
|
||||
IOException, ServletException {
|
||||
rsp.setContentType(MEDIA_TYPE_APPLICATION_JSON);
|
||||
rsp.getWriter().print(JsonConverter.toJson(value));
|
||||
rsp.setStatus(status);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static HttpResponse json(final Object value) {
|
||||
return new HttpResponse() {
|
||||
public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node) throws
|
||||
IOException, ServletException {
|
||||
rsp.setContentType(MEDIA_TYPE_APPLICATION_JSON);
|
||||
rsp.getWriter().print(JsonConverter.toJson(value));
|
||||
rsp.setStatus(200);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package io.jenkins.blueocean.rest;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.jenkins.blueocean.commons.ServiceException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Query syntax
|
||||
*
|
||||
* Give all pipelines for acme organization
|
||||
*
|
||||
* <pre>/search?q=type:pipeline organization:acme</pre>
|
||||
*<p>
|
||||
* Give all Runs for pipelineA in organization acme
|
||||
*
|
||||
* <pre>/search?q=type:run pipeline:pipelineA organization:acme</pre>
|
||||
*
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
public class Query {
|
||||
|
||||
public final String type;
|
||||
private final Map<String, String> params;
|
||||
|
||||
private Query(String type, Map<String, String> values) {
|
||||
this.type = type;
|
||||
this.params = (values == null) ? ImmutableMap.<String, String>of() : ImmutableMap.copyOf(values);
|
||||
}
|
||||
|
||||
public String param(String key){
|
||||
return params.get(key);
|
||||
}
|
||||
|
||||
public String param(String key, boolean required){
|
||||
return param(key, String.class, required);
|
||||
}
|
||||
|
||||
public <T> T param(String key, Class<T> type){
|
||||
return param(key, type, false);
|
||||
}
|
||||
|
||||
public <T> T param(String key, Class<T> type, boolean required){
|
||||
String value = params.get(key);
|
||||
if(value == null && required){
|
||||
throw new ServiceException.BadRequestExpception(
|
||||
String.format("%s is required parameter to execute query for type %s", key, type));
|
||||
}else if (value == null){
|
||||
return null;
|
||||
}
|
||||
return Utils.cast(value, type);
|
||||
}
|
||||
|
||||
public static Query parse(String query) {
|
||||
String[] values = query.split(";");
|
||||
|
||||
String type = null;
|
||||
Map<String, String> params = new HashMap<>();
|
||||
for (String v : values) {
|
||||
String[] vv = v.split(":");
|
||||
if (vv.length != 2) {
|
||||
continue;
|
||||
}
|
||||
String key = vv[0].trim().toLowerCase();
|
||||
String value = vv[1].trim();
|
||||
if (key.equals("type")) {
|
||||
type = value.toLowerCase();
|
||||
} else {
|
||||
params.put(key, value);
|
||||
}
|
||||
}
|
||||
//If there is no type, we don't know what resource to look for
|
||||
//TODO: what would be default type?
|
||||
if (type == null) {
|
||||
throw new ServiceException.BadRequestExpception("type is required parameter for query param q");
|
||||
}
|
||||
return new Query(type, params);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,448 @@
|
|||
package io.jenkins.blueocean.rest;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import hudson.util.QueryParameterMap;
|
||||
import io.jenkins.blueocean.commons.JsonConverter;
|
||||
import io.jenkins.blueocean.commons.ServiceException;
|
||||
import io.jenkins.blueocean.rest.router.RouteMatch;
|
||||
import io.jenkins.blueocean.security.Identity;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.Principal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
public class Request {
|
||||
private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(Request.class);
|
||||
|
||||
private static final String USER_AGENT = "user-agent";
|
||||
|
||||
private final Map<String, String> params;
|
||||
private final List<String> splat;
|
||||
private final QueryParameterMap queryMap;
|
||||
|
||||
private final HttpServletRequest servletRequest;
|
||||
|
||||
private Session session = null;
|
||||
|
||||
private final Set<String> headers = new HashSet<>();
|
||||
|
||||
private InputStream body;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param match the route match
|
||||
* @param request the servlet request
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public Request(RouteMatch match, HttpServletRequest request) {
|
||||
this.servletRequest = request;
|
||||
Enumeration<String> enumeration = servletRequest.getHeaderNames();
|
||||
while (enumeration.hasMoreElements()) {
|
||||
headers.add(enumeration.nextElement());
|
||||
}
|
||||
this.queryMap = new QueryParameterMap(request);
|
||||
List<String> requestList = Utils.convertRouteToList(match.getRequestURI());
|
||||
List<String> matchedList = Utils.convertRouteToList(match.getMatchUri());
|
||||
|
||||
this.params = getParams(requestList, matchedList);
|
||||
this.splat = getSplat(requestList, matchedList);
|
||||
try {
|
||||
this.body = request.getInputStream();
|
||||
} catch (IOException e) {
|
||||
throw new ServiceException.UnexpectedErrorExpcetion("Failed to read request input stream: "+e.getMessage(), e);
|
||||
}
|
||||
this.session = new Session(servletRequest.getSession());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the map containing all route pathParam
|
||||
*
|
||||
* @return a map containing all route pathParam
|
||||
*/
|
||||
public Map<String, String> pathParam() {
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the provided route pattern parameter.
|
||||
* Example: parameter 'name' from the following pattern: (get '/hello/:name')
|
||||
*
|
||||
* @param param the param
|
||||
* @return null if the given param is null or not found
|
||||
*/
|
||||
public String pathParam(String param) {
|
||||
if (param == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (param.startsWith(":")) {
|
||||
return params.get(param.toLowerCase()); // NOSONAR
|
||||
} else {
|
||||
return params.get(":" + param.toLowerCase()); // NOSONAR
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T pathParam(String param, Class<T> type) {
|
||||
if (param == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String value;
|
||||
if (param.startsWith(":")) {
|
||||
value = params.get(param.toLowerCase()); // NOSONAR
|
||||
} else {
|
||||
value = params.get(":" + param.toLowerCase()); // NOSONAR
|
||||
}
|
||||
return Utils.cast(value, type);
|
||||
}
|
||||
|
||||
public String queryParam(@Nonnull String key) {
|
||||
return queryParam(key, String.class, false);
|
||||
}
|
||||
|
||||
|
||||
public String queryParam(@Nonnull String key, boolean required) {
|
||||
return queryParam(key, String.class, required);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T queryParam(@Nonnull String key, @Nonnull Class<T> type) {
|
||||
return queryParam(key, type, false);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T queryParam(@Nonnull String key, @Nonnull Class<T> type, boolean required) {
|
||||
String value = queryMap.get(key);
|
||||
if (value == null && required) {
|
||||
throw new ServiceException.BadRequestExpception(String.format("Query param %s is required", key));
|
||||
}
|
||||
if (value == null) {
|
||||
if (Boolean.class.isAssignableFrom(type)) {
|
||||
return (T) Boolean.FALSE;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return Utils.cast(value, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an array containing the splat (wildcard) parameters
|
||||
*/
|
||||
public String[] splat() {
|
||||
return splat.toArray(new String[splat.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return request method e.g. GET, POST, PUT, ...
|
||||
*/
|
||||
public String requestMethod() {
|
||||
return servletRequest.getMethod();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the scheme
|
||||
*/
|
||||
public String scheme() {
|
||||
return servletRequest.getScheme();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the host
|
||||
*/
|
||||
public String host() {
|
||||
return servletRequest.getHeader("host");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the user-agent
|
||||
*/
|
||||
public String userAgent() {
|
||||
return servletRequest.getHeader(USER_AGENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the server port
|
||||
*/
|
||||
public int port() {
|
||||
return servletRequest.getServerPort();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the path info
|
||||
* Example return: "/example/foo"
|
||||
*/
|
||||
public String pathInfo() {
|
||||
return servletRequest.getPathInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the servlet path
|
||||
*/
|
||||
public String servletPath() {
|
||||
return servletRequest.getServletPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the context path
|
||||
*/
|
||||
public String contextPath() {
|
||||
return servletRequest.getContextPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the URL string
|
||||
*/
|
||||
public String url() {
|
||||
return servletRequest.getRequestURL().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the content type of the body
|
||||
*/
|
||||
public String contentType() {
|
||||
return servletRequest.getContentType();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the client's IP address
|
||||
*/
|
||||
public String ip() {
|
||||
return servletRequest.getRemoteAddr();
|
||||
}
|
||||
|
||||
public Identity principal(){
|
||||
Principal principal = servletRequest.getUserPrincipal();
|
||||
if(principal != null && principal instanceof Identity){
|
||||
return (Identity) principal;
|
||||
}else{
|
||||
return Identity.ANONYMOUS;
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T body(@Nonnull String contentType, @Nonnull Class<T> type){
|
||||
if(contentType.toLowerCase().startsWith("application/json")){
|
||||
return JsonConverter.toJava(body, type);
|
||||
}else{
|
||||
throw new ServiceException.UnsupportedMediaTypeException("Only application/json supported at the moment");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the length of request.body
|
||||
*/
|
||||
public int contentLength() {
|
||||
return servletRequest.getContentLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value for the provided header
|
||||
*
|
||||
* @param header the header
|
||||
* @return the value of the provided header
|
||||
*/
|
||||
public String headers(String header) {
|
||||
return servletRequest.getHeader(header);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all headers
|
||||
*/
|
||||
public Set<String> headers() {
|
||||
return ImmutableSet.copyOf(headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the query string
|
||||
*/
|
||||
public String queryString() {
|
||||
return servletRequest.getQueryString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an attribute on the request (can be fetched in filters/routes later in the chain)
|
||||
*
|
||||
* @param attribute The attribute
|
||||
* @param value The attribute value
|
||||
*/
|
||||
public void attribute(String attribute, Object value) {
|
||||
servletRequest.setAttribute(attribute, value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @return all attributes
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public Set<String> attributes() {
|
||||
Set<String> attrList = new HashSet<String>();
|
||||
Enumeration<String> attributes = (Enumeration<String>) servletRequest.getAttributeNames();
|
||||
while (attributes.hasMoreElements()) {
|
||||
attrList.add(attributes.nextElement());
|
||||
}
|
||||
return attrList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the raw HttpServletRequest object handed in by servlet container
|
||||
*/
|
||||
public HttpServletRequest raw() {
|
||||
return servletRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the query map
|
||||
*/
|
||||
public QueryParameterMap queryMap() {
|
||||
return queryMap;
|
||||
}
|
||||
|
||||
public boolean hasQueryParameter(){
|
||||
return servletRequest.getParameterNames().hasMoreElements();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current session associated with this request,
|
||||
* or if the request does not have a session, creates one.
|
||||
*
|
||||
* @return the session associated with this request
|
||||
*/
|
||||
public Session session() {
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current session associated with this request, or if there is
|
||||
* no current session and <code>create</code> is true, returns a new session.
|
||||
*
|
||||
* @param create <code>true</code> to create a new session for this request if necessary;
|
||||
* <code>false</code> to return null if there's no current session
|
||||
* @return the session associated with this request or <code>null</code> if
|
||||
* <code>create</code> is <code>false</code> and the request has no valid session
|
||||
*/
|
||||
public Session session(boolean create) {
|
||||
if (session == null) {
|
||||
HttpSession httpSession = servletRequest.getSession(create);
|
||||
if (httpSession != null) {
|
||||
session = new Session(httpSession);
|
||||
}
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return request cookies (or empty Map if cookies aren't present)
|
||||
*/
|
||||
public Map<String, String> cookies() {
|
||||
Map<String, String> result = new HashMap<String, String>();
|
||||
Cookie[] cookies = servletRequest.getCookies();
|
||||
if (cookies != null) {
|
||||
for (Cookie cookie : cookies) {
|
||||
result.put(cookie.getName(), cookie.getValue());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets cookie by name.
|
||||
*
|
||||
* @param name name of the cookie
|
||||
* @return cookie value or null if the cookie was not found
|
||||
*/
|
||||
public String cookie(String name) {
|
||||
Cookie[] cookies = servletRequest.getCookies();
|
||||
if (cookies != null) {
|
||||
for (Cookie cookie : cookies) {
|
||||
if (cookie.getName().equals(name)) {
|
||||
return cookie.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the part of this request's URL from the protocol name up to the query string in the first line of the HTTP request.
|
||||
*/
|
||||
public String uri() {
|
||||
return servletRequest.getRequestURI();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the name and version of the protocol the request uses
|
||||
*/
|
||||
public String protocol() {
|
||||
return servletRequest.getProtocol();
|
||||
}
|
||||
|
||||
/** Returns unmodifiable param map */
|
||||
private static Map<String, String> getParams(List<String> request, List<String> matched) {
|
||||
LOG.debug("get pathParam");
|
||||
|
||||
Map<String, String> params = new HashMap<String, String>();
|
||||
|
||||
for (int i = 0; (i < request.size()) && (i < matched.size()); i++) {
|
||||
String matchedPart = matched.get(i);
|
||||
if (Utils.isParam(matchedPart)) {
|
||||
LOG.debug("matchedPart: "
|
||||
+ matchedPart
|
||||
+ " = "
|
||||
+ request.get(i));
|
||||
params.put(matchedPart.toLowerCase(), request.get(i));
|
||||
}
|
||||
}
|
||||
return ImmutableMap.copyOf(params);
|
||||
}
|
||||
|
||||
private static List<String> getSplat(List<String> request, List<String> matched) {
|
||||
LOG.debug("get splat");
|
||||
|
||||
int nbrOfRequestParts = request.size();
|
||||
int nbrOfMatchedParts = matched.size();
|
||||
|
||||
boolean sameLength = (nbrOfRequestParts == nbrOfMatchedParts);
|
||||
|
||||
List<String> splat = new ArrayList<String>();
|
||||
|
||||
for (int i = 0; (i < nbrOfRequestParts) && (i < nbrOfMatchedParts); i++) {
|
||||
String matchedPart = matched.get(i);
|
||||
|
||||
if (Utils.isSplat(matchedPart)) {
|
||||
|
||||
StringBuilder splatParam = new StringBuilder(request.get(i));
|
||||
if (!sameLength && (i == (nbrOfMatchedParts - 1))) {
|
||||
for (int j = i + 1; j < nbrOfRequestParts; j++) {
|
||||
splatParam.append("/");
|
||||
splatParam.append(request.get(j));
|
||||
}
|
||||
}
|
||||
splat.add(splatParam.toString());
|
||||
}
|
||||
}
|
||||
return ImmutableList.copyOf(splat);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
package io.jenkins.blueocean.rest;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Provides functionality for modifying the response
|
||||
*
|
||||
* @author Per Wendel
|
||||
*/
|
||||
public class Response {
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Response.class);
|
||||
|
||||
private HttpServletResponse response;
|
||||
private String body;
|
||||
|
||||
protected Response() {
|
||||
// Used by wrapper
|
||||
}
|
||||
|
||||
Response(HttpServletResponse response) {
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the status code for the
|
||||
*
|
||||
* @param statusCode the status code
|
||||
*/
|
||||
public void status(int statusCode) {
|
||||
response.setStatus(statusCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the content type for the response
|
||||
*
|
||||
* @param contentType the content type
|
||||
*/
|
||||
public void type(String contentType) {
|
||||
response.setContentType(contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the body
|
||||
*
|
||||
* @param body the body
|
||||
*/
|
||||
public void body(String body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the body
|
||||
*
|
||||
* @return the body
|
||||
*/
|
||||
public String body() {
|
||||
return this.body;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the raw response object handed in by Jetty
|
||||
*/
|
||||
public HttpServletResponse raw() {
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a browser redirect
|
||||
*
|
||||
* @param location Where to redirect
|
||||
*/
|
||||
public void redirect(String location) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Redirecting ({} {} to {}", "Found", HttpServletResponse.SC_FOUND, location);
|
||||
}
|
||||
try {
|
||||
response.sendRedirect(location);
|
||||
} catch (IOException ioException) {
|
||||
LOG.warn("Redirect failure", ioException);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a browser redirect with specific http 3XX status code.
|
||||
*
|
||||
* @param location Where to redirect permanently
|
||||
* @param httpStatusCode the http status code
|
||||
*/
|
||||
public void redirect(String location, int httpStatusCode) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Redirecting ({} to {}", httpStatusCode, location);
|
||||
}
|
||||
response.setStatus(httpStatusCode);
|
||||
response.setHeader("Location", location);
|
||||
response.setHeader("Connection", "close");
|
||||
try {
|
||||
response.sendError(httpStatusCode);
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Exception when trying to redirect permanently", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds/Sets a response header
|
||||
*
|
||||
* @param header the header
|
||||
* @param value the value
|
||||
*/
|
||||
public void header(String header, String value) {
|
||||
response.addHeader(header, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds not persistent cookie to the response.
|
||||
* Can be invoked multiple times to insert more than one cookie.
|
||||
*
|
||||
* @param name name of the cookie
|
||||
* @param value value of the cookie
|
||||
*/
|
||||
public void cookie(String name, String value) {
|
||||
cookie(name, value, -1, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds cookie to the response. Can be invoked multiple times to insert more than one cookie.
|
||||
*
|
||||
* @param name name of the cookie
|
||||
* @param value value of the cookie
|
||||
* @param maxAge max age of the cookie in seconds (negative for the not persistent cookie,
|
||||
* zero - deletes the cookie)
|
||||
*/
|
||||
public void cookie(String name, String value, int maxAge) {
|
||||
cookie(name, value, maxAge, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds cookie to the response. Can be invoked multiple times to insert more than one cookie.
|
||||
*
|
||||
* @param name name of the cookie
|
||||
* @param value value of the cookie
|
||||
* @param maxAge max age of the cookie in seconds (negative for the not persistent cookie, zero - deletes the cookie)
|
||||
* @param secured if true : cookie will be secured
|
||||
*/
|
||||
public void cookie(String name, String value, int maxAge, boolean secured) {
|
||||
cookie(name, value, maxAge, secured, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds cookie to the response. Can be invoked multiple times to insert more than one cookie.
|
||||
*
|
||||
* @param name name of the cookie
|
||||
* @param value value of the cookie
|
||||
* @param maxAge max age of the cookie in seconds (negative for the not persistent cookie, zero - deletes the cookie)
|
||||
* @param secured if true : cookie will be secured
|
||||
* @param httpOnly if true: cookie will be marked as http only
|
||||
*/
|
||||
public void cookie(String name, String value, int maxAge, boolean secured, boolean httpOnly) {
|
||||
cookie("", name, value, maxAge, secured, httpOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds cookie to the response. Can be invoked multiple times to insert more than one cookie.
|
||||
*
|
||||
* @param path path of the cookie
|
||||
* @param name name of the cookie
|
||||
* @param value value of the cookie
|
||||
* @param maxAge max age of the cookie in seconds (negative for the not persistent cookie, zero - deletes the cookie)
|
||||
* @param secured if true : cookie will be secured
|
||||
*/
|
||||
public void cookie(String path, String name, String value, int maxAge, boolean secured) {
|
||||
cookie(path, name, value, maxAge, secured, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds cookie to the response. Can be invoked multiple times to insert more than one cookie.
|
||||
*
|
||||
* @param path path of the cookie
|
||||
* @param name name of the cookie
|
||||
* @param value value of the cookie
|
||||
* @param maxAge max age of the cookie in seconds (negative for the not persistent cookie, zero - deletes the cookie)
|
||||
* @param secured if true : cookie will be secured
|
||||
* @param httpOnly if true: cookie will be marked as http only
|
||||
*/
|
||||
public void cookie(String path, String name, String value, int maxAge, boolean secured, boolean httpOnly) {
|
||||
Cookie cookie = new Cookie(name, value);
|
||||
cookie.setPath(path);
|
||||
cookie.setMaxAge(maxAge);
|
||||
cookie.setSecure(secured);
|
||||
//TODO: Cookie.setHttpOnly() is servlet 3.0, get HttpOnly header written at Set-Cookie header somewhere else
|
||||
// cookie.setHttpOnly(httpOnly);
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the cookie.
|
||||
*
|
||||
* @param name name of the cookie
|
||||
*/
|
||||
public void removeCookie(String name) {
|
||||
Cookie cookie = new Cookie(name, "");
|
||||
cookie.setMaxAge(0);
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
package io.jenkins.blueocean.rest;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* Provides session information.
|
||||
* Copied and modified from <a href="https://github.com/perwendel/spark">Sinatra inspired Spark</a>
|
||||
*/
|
||||
public class Session {
|
||||
|
||||
private final HttpSession session;
|
||||
|
||||
/**
|
||||
* Creates a session with the <code>HttpSession</code>.
|
||||
*
|
||||
* @param session
|
||||
* @throws IllegalArgumentException If the session is null.
|
||||
*/
|
||||
Session(HttpSession session) {
|
||||
if (session == null) {
|
||||
throw new IllegalArgumentException("session cannot be null");
|
||||
}
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the raw <code>HttpSession</code> object handed in by the servlet container.
|
||||
*/
|
||||
public HttpSession raw() {
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object bound with the specified name in this session, or null if no object is bound under the name.
|
||||
*
|
||||
* @param name a string specifying the name of the object
|
||||
* @param <T> The type parameter
|
||||
* @return the object with the specified name
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T attribute(String name) {
|
||||
return (T) session.getAttribute(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds an object to this session, using the name specified.
|
||||
*
|
||||
* @param name the name to which the object is bound; cannot be null
|
||||
* @param value the object to be bound
|
||||
*/
|
||||
public void attribute(String name, Object value) {
|
||||
session.setAttribute(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an <code>Enumeration</code> of <code>String</code> objects
|
||||
* containing the names of all the objects bound to this session.
|
||||
*/
|
||||
public Set<String> attributes() {
|
||||
TreeSet<String> attributes = new TreeSet<String>();
|
||||
Enumeration<String> enumeration = session.getAttributeNames();
|
||||
while (enumeration.hasMoreElements()) {
|
||||
attributes.add(enumeration.nextElement());
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the time when this session was created, measured in milliseconds since midnight January 1, 1970 GMT.
|
||||
*/
|
||||
public long creationTime() {
|
||||
return session.getCreationTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a string containing the unique identifier assigned to this session.
|
||||
*/
|
||||
public String id() {
|
||||
return session.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the last time the client sent a request associated with this session,
|
||||
* as the number of milliseconds since midnight January 1, 1970 GMT, and marked
|
||||
* by the time the container received the request.
|
||||
*/
|
||||
public long lastAccessedTime() {
|
||||
return session.getLastAccessedTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the maximum time interval, in seconds, that the container
|
||||
* will keep this session open between client accesses.
|
||||
*/
|
||||
public int maxInactiveInterval() {
|
||||
return session.getMaxInactiveInterval();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the time, in seconds, between client requests the web container will invalidate this session.
|
||||
*
|
||||
* @param interval the interval
|
||||
*/
|
||||
public void maxInactiveInterval(int interval) {
|
||||
session.setMaxInactiveInterval(interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates this session then unbinds any objects bound to it.
|
||||
*/
|
||||
public void invalidate() {
|
||||
session.invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the client does not yet know about the session or if the client chooses not to join the session.
|
||||
*/
|
||||
public boolean isNew() {
|
||||
return session.isNew();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the object bound with the specified name from this session.
|
||||
*
|
||||
* @param name the name of the object to remove from this session
|
||||
*/
|
||||
public void removeAttribute(String name) {
|
||||
session.removeAttribute(name);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package io.jenkins.blueocean.rest;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import io.jenkins.blueocean.commons.ServiceException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
public class Utils {
|
||||
public static final String ALL_PATHS = "+/*paths";
|
||||
|
||||
public static List<String> convertRouteToList(String route) {
|
||||
String[] pathArray = route.split("/");
|
||||
List<String> path = new ArrayList<String>();
|
||||
for (String p : pathArray) {
|
||||
if (p.length() > 0) {
|
||||
path.add(p);
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
public static boolean isParam(String routePart) {
|
||||
return routePart.startsWith(":");
|
||||
}
|
||||
|
||||
public static boolean isSplat(String routePart) {
|
||||
return routePart.equals("*");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T cast(String value, Class<T> type){
|
||||
try{
|
||||
if(String.class.isAssignableFrom(type)){
|
||||
return (T)value;
|
||||
}else if(Integer.class.isAssignableFrom(type)){
|
||||
return (T)Integer.valueOf(value);
|
||||
}else if(Long.class.isAssignableFrom(type)){
|
||||
return (T)Long.valueOf(value);
|
||||
}else if(Boolean.class.isAssignableFrom(type)){
|
||||
return (T)Boolean.valueOf(value);
|
||||
}else if(List.class.isAssignableFrom(type)){ //List<String>
|
||||
String[] vs = value.split(",");
|
||||
List<String> list = new ArrayList<>();
|
||||
for(String v: vs){
|
||||
list.add(v.trim());
|
||||
}
|
||||
return (T)ImmutableList.of(list);
|
||||
}else if(Set.class.isAssignableFrom(type)){ //List<String>
|
||||
String[] vs = value.split(",");
|
||||
Set<String> set = new HashSet<>();
|
||||
for(String v: vs){
|
||||
set.add(v.trim());
|
||||
}
|
||||
return (T) ImmutableSet.of(set);
|
||||
}else{
|
||||
throw new ServiceException.UnexpectedErrorExpcetion(
|
||||
String.format("Unknown type %s", type));
|
||||
}
|
||||
}catch (NumberFormatException e){
|
||||
throw new ServiceException.BadRequestExpception(
|
||||
String.format("Value %s can't be converted to type: %s", value, type));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,232 @@
|
|||
package io.jenkins.blueocean.rest.router;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* MIME-Type Parser
|
||||
*
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
class MimeParser {
|
||||
|
||||
private MimeParser() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constant for no mime type
|
||||
*/
|
||||
public static final String NO_MIME_TYPE = "";
|
||||
|
||||
public static final String APPLICATION_JSON = "application/json";
|
||||
|
||||
/**
|
||||
* Parse results container
|
||||
*/
|
||||
private static class ParseResults {
|
||||
String type;
|
||||
|
||||
String subType;
|
||||
|
||||
// !a dictionary of all the parameters for the media range
|
||||
Map<String, String> params;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder s = new StringBuilder("('" + type + "', '" + subType + "', {");
|
||||
for (Map.Entry<String,String> k : params.entrySet()) {
|
||||
s.append("'").append(k.getKey()).append("':'").append(k.getValue()).append("',");
|
||||
}
|
||||
return s.append("})").toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Carves up a mime-type and returns a ParseResults object
|
||||
* For example, the media range 'application/xhtml;q=0.5' would get parsed
|
||||
* into:
|
||||
* ('application', 'xhtml', {'q', '0.5'})
|
||||
*/
|
||||
private static ParseResults parseMimeType(String mimeType) {
|
||||
String[] parts = mimeType.split(";");
|
||||
ParseResults results = new ParseResults();
|
||||
results.params = new HashMap<>();
|
||||
|
||||
for (int i = 1; i < parts.length; ++i) {
|
||||
String p = parts[i];
|
||||
String[] subParts = p.split("=");
|
||||
if (subParts.length == 2) {
|
||||
results.params.put(subParts[0].trim(), subParts[1].trim());
|
||||
}
|
||||
}
|
||||
String fullType = parts[0].trim();
|
||||
|
||||
// Java URLConnection class sends an Accept header that includes a
|
||||
// single "*" - Turn it into a legal wildcard.
|
||||
if (fullType.equals("*")) {
|
||||
fullType = "*/*";
|
||||
}
|
||||
|
||||
int slashIndex = fullType.indexOf('/');
|
||||
if (slashIndex != -1) {
|
||||
results.type = fullType.substring(0, slashIndex);
|
||||
results.subType = fullType.substring(slashIndex + 1);
|
||||
} else {
|
||||
//If the type is invalid, attempt to turn into a wildcard
|
||||
results.type = fullType;
|
||||
results.subType = "*";
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Carves up a media range and returns a ParseResults.
|
||||
* For example, the media range 'application/*;q=0.5' would get parsed into:
|
||||
* ('application', '*', {'q', '0.5'})
|
||||
* In addition this function also guarantees that there is a value for 'q'
|
||||
* in the pathParam dictionary, filling it in with a proper default if
|
||||
* necessary.
|
||||
*
|
||||
* @param range
|
||||
*/
|
||||
private static ParseResults parseMediaRange(String range) {
|
||||
ParseResults results = parseMimeType(range);
|
||||
String q = results.params.get("q");
|
||||
float f = toFloat(q, 1);
|
||||
if (isBlank(q) || f < 0 || f > 1) {
|
||||
results.params.put("q", "1");
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure for holding a fitness/quality combo
|
||||
*/
|
||||
private static class FitnessAndQuality implements Comparable<FitnessAndQuality> {
|
||||
int fitness;
|
||||
|
||||
float quality;
|
||||
|
||||
String mimeType; // optionally used
|
||||
|
||||
private FitnessAndQuality(int fitness, float quality) {
|
||||
this.fitness = fitness;
|
||||
this.quality = quality;
|
||||
}
|
||||
|
||||
public int compareTo(FitnessAndQuality o) {
|
||||
if (fitness == o.fitness) {
|
||||
if (quality == o.quality) {
|
||||
return 0;
|
||||
} else {
|
||||
return Float.compare(quality,o.quality) < 0 ? -1 : 1;
|
||||
}
|
||||
} else {
|
||||
return (fitness < o.fitness) ? -1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (!(obj instanceof FitnessAndQuality))
|
||||
return false;
|
||||
FitnessAndQuality other = (FitnessAndQuality) obj;
|
||||
return fitness == other.fitness && quality == other.quality;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hashCode = 1;
|
||||
hashCode = (int) (31 * hashCode + quality);
|
||||
hashCode = 31 * hashCode + fitness;
|
||||
hashCode = 31 * hashCode + mimeType.hashCode();
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the best match for a given mimeType against a list of media_ranges
|
||||
* that have already been parsed by MimeParse.parseMediaRange(). Returns a
|
||||
* tuple of the fitness value and the value of the 'q' quality parameter of
|
||||
* the best match, or (-1, 0) if no match was found. Just as for
|
||||
* quality_parsed(), 'parsed_ranges' must be a list of parsed media ranges.
|
||||
*
|
||||
* @param mimeType
|
||||
* @param parsedRanges
|
||||
*/
|
||||
private static FitnessAndQuality fitnessAndQualityParsed(String mimeType, Collection<ParseResults> parsedRanges) {
|
||||
int bestFitness = -1;
|
||||
float bestFitQ = 0;
|
||||
ParseResults target = parseMediaRange(mimeType);
|
||||
|
||||
for (ParseResults range : parsedRanges) {
|
||||
if ((target.type.equals(range.type) || range.type.equals("*") || target.type.equals("*"))
|
||||
&& (target.subType.equals(range.subType) || range.subType.equals("*")
|
||||
|| target.subType.equals("*"))) {
|
||||
for (String k : target.params.keySet()) {
|
||||
int paramMatches = 0;
|
||||
if (!k.equals("q") && range.params.containsKey(k)
|
||||
&& target.params.get(k).equals(range.params.get(k))) {
|
||||
paramMatches++;
|
||||
}
|
||||
int fitness = (range.type.equals(target.type)) ? 100 : 0;
|
||||
fitness += (range.subType.equals(target.subType)) ? 10 : 0;
|
||||
fitness += paramMatches;
|
||||
if (fitness > bestFitness) {
|
||||
bestFitness = fitness;
|
||||
bestFitQ = toFloat(range.params.get("q"), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new FitnessAndQuality(bestFitness, bestFitQ);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds best match
|
||||
*
|
||||
* @param supported the supported types
|
||||
* @param header the header
|
||||
* @return the best match
|
||||
*/
|
||||
public static String bestMatch(Collection<String> supported, String header) {
|
||||
List<ParseResults> parseResults = new LinkedList<ParseResults>();
|
||||
List<FitnessAndQuality> weightedMatches = new LinkedList<FitnessAndQuality>();
|
||||
for (String r : header.split(",")) {
|
||||
parseResults.add(parseMediaRange(r));
|
||||
}
|
||||
|
||||
for (String s : supported) {
|
||||
FitnessAndQuality fitnessAndQuality = fitnessAndQualityParsed(s, parseResults);
|
||||
fitnessAndQuality.mimeType = s;
|
||||
weightedMatches.add(fitnessAndQuality);
|
||||
}
|
||||
Collections.sort(weightedMatches);
|
||||
|
||||
FitnessAndQuality lastOne = weightedMatches.get(weightedMatches.size() - 1);
|
||||
return Float.compare(lastOne.quality, 0) != 0 ? lastOne.mimeType : NO_MIME_TYPE;
|
||||
}
|
||||
|
||||
private static boolean isBlank(String s) {
|
||||
return s == null || "".equals(s.trim());
|
||||
}
|
||||
|
||||
private static float toFloat(final String str, final float defaultValue) {
|
||||
if (str == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
try {
|
||||
return Float.parseFloat(str);
|
||||
} catch (final NumberFormatException nfe) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package io.jenkins.blueocean.rest.router;
|
||||
|
||||
import io.jenkins.blueocean.commons.ServiceException;
|
||||
import io.jenkins.blueocean.rest.Request;
|
||||
import io.jenkins.blueocean.rest.Response;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Any REST resource that can handle an HTTP route gets chance to do it by implementing this interface.
|
||||
*
|
||||
* Inspired from <a href="https://github.com/perwendel/spark">Sinatra inspired Spark</a>
|
||||
*
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
public interface Route {
|
||||
|
||||
String DEFAULT_ACCEPT_TYPE = "*/*";
|
||||
String DEFAULT_CONTENT_TYPE = "application/json";
|
||||
|
||||
|
||||
/**
|
||||
* Invoked when a request is made on this route's corresponding path e.g. '/hello'
|
||||
*
|
||||
* @param request The request object providing information about the HTTP request
|
||||
* @param response The response object providing functionality for modifying the response
|
||||
* @return The content to be set in the response
|
||||
* @throws ServiceException if there is any exception
|
||||
*/
|
||||
Object handle(Request request, Response response);
|
||||
|
||||
/**
|
||||
* Gives URL path this Route can handle. e.g. /organizations/:organization-id
|
||||
*
|
||||
* @return URL path
|
||||
*/
|
||||
@Nonnull String path();
|
||||
|
||||
/**
|
||||
* Gives accessType this route can consume. Incoming requests's Access header will be matched against the
|
||||
* Route's accessType and if it doesn't match then an error will be generated with 406 Not Acceptable error.
|
||||
*
|
||||
* @return MIME type the route can
|
||||
*/
|
||||
@Nonnull String accessType();
|
||||
|
||||
/**
|
||||
* Content type this route produces
|
||||
*
|
||||
* @return content type
|
||||
*/
|
||||
String contentType();
|
||||
|
||||
abstract class RouteImpl implements Route{
|
||||
private final String path;
|
||||
private final String acceptType;
|
||||
private final String contentType;
|
||||
|
||||
public RouteImpl(String path) {
|
||||
this.path = path;
|
||||
this.acceptType = DEFAULT_ACCEPT_TYPE;
|
||||
this.contentType = DEFAULT_CONTENT_TYPE;
|
||||
}
|
||||
|
||||
public RouteImpl(String path, String acceptType) {
|
||||
this.path = path;
|
||||
this.acceptType = acceptType;
|
||||
this.contentType = DEFAULT_CONTENT_TYPE;
|
||||
}
|
||||
|
||||
public RouteImpl(String path, String acceptType, String contentType) {
|
||||
this.path = path;
|
||||
this.acceptType = acceptType;
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String path() {
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String accessType() {
|
||||
return acceptType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String contentType() {
|
||||
return contentType;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package io.jenkins.blueocean.rest.router;
|
||||
|
||||
/**
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
|
||||
import io.jenkins.blueocean.rest.Body;
|
||||
import io.jenkins.blueocean.rest.HttpMethod;
|
||||
import io.jenkins.blueocean.rest.Response;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* Holds the parameters needed in the Before filters, Routes and After filters execution.
|
||||
*/
|
||||
public final class RouteContext {
|
||||
|
||||
/**
|
||||
* Creates a RouteContext
|
||||
*/
|
||||
public static RouteContext create() {
|
||||
return new RouteContext();
|
||||
}
|
||||
|
||||
private RouteMatcher routeMatcher;
|
||||
private HttpServletRequest httpRequest;
|
||||
private String uri;
|
||||
private String acceptType;
|
||||
private Body body;
|
||||
private Response response;
|
||||
private HttpMethod httpMethod;
|
||||
|
||||
private RouteContext() {
|
||||
// hidden
|
||||
}
|
||||
|
||||
public RouteMatcher routeMatcher() {
|
||||
return routeMatcher;
|
||||
}
|
||||
|
||||
public RouteContext withMatcher(RouteMatcher routeMatcher) {
|
||||
this.routeMatcher = routeMatcher;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RouteContext withHttpRequest(HttpServletRequest httpRequest) {
|
||||
this.httpRequest = httpRequest;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RouteContext withAcceptType(String acceptType) {
|
||||
this.acceptType = acceptType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RouteContext withBody(Body body) {
|
||||
this.body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public RouteContext withUri(String uri) {
|
||||
this.uri = uri;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RouteContext withResponse(Response response) {
|
||||
this.response = response;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RouteContext withHttpMethod(HttpMethod httpMethod) {
|
||||
this.httpMethod = httpMethod;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpServletRequest httpRequest() {
|
||||
return httpRequest;
|
||||
}
|
||||
|
||||
public String uri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
public String acceptType() {
|
||||
return acceptType;
|
||||
}
|
||||
|
||||
public Body body() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public Response response() {
|
||||
return response;
|
||||
}
|
||||
|
||||
public HttpMethod httpMethod() {
|
||||
return httpMethod;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
package io.jenkins.blueocean.rest.router;
|
||||
|
||||
import io.jenkins.blueocean.rest.HttpMethod;
|
||||
import io.jenkins.blueocean.rest.Utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Per Wendel
|
||||
*/
|
||||
class RouteEntry {
|
||||
|
||||
final HttpMethod httpMethod;
|
||||
final String path;
|
||||
final String acceptedType;
|
||||
final Route target;
|
||||
|
||||
public RouteEntry(HttpMethod httpMethod, String path, String acceptedType, Route target) {
|
||||
this.httpMethod = httpMethod;
|
||||
this.path = path;
|
||||
this.acceptedType = acceptedType;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
boolean matches(HttpMethod httpMethod, String path) {
|
||||
if ((httpMethod == HttpMethod.before || httpMethod == HttpMethod.after)
|
||||
&& (this.httpMethod == httpMethod)
|
||||
&& this.path.equals(Utils.ALL_PATHS)) {
|
||||
// Is filter and matches all
|
||||
return true;
|
||||
}
|
||||
boolean match = false;
|
||||
if (this.httpMethod == httpMethod) {
|
||||
match = matchPath(path);
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
private boolean matchPath(String path) {
|
||||
if (!this.path.endsWith("*")
|
||||
&& ((path.endsWith("/") && !this.path.endsWith("/"))
|
||||
|| (this.path.endsWith("/") && !path.endsWith("/")))) {
|
||||
// One and not both ends with slash
|
||||
return false;
|
||||
}
|
||||
if (this.path.equals(path)) {
|
||||
// Paths are the same
|
||||
return true;
|
||||
}
|
||||
|
||||
// check pathParam
|
||||
List<String> thisPathList = Utils.convertRouteToList(this.path);
|
||||
List<String> pathList = Utils.convertRouteToList(path);
|
||||
|
||||
int thisPathSize = thisPathList.size();
|
||||
int pathSize = pathList.size();
|
||||
|
||||
if (thisPathSize == pathSize) {
|
||||
for (int i = 0; i < thisPathSize; i++) {
|
||||
String thisPathPart = thisPathList.get(i);
|
||||
String pathPart = pathList.get(i);
|
||||
|
||||
if ((i == thisPathSize - 1) && (thisPathPart.equals("*") && this.path.endsWith("*"))) {
|
||||
// wildcard match
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((!thisPathPart.startsWith(":"))
|
||||
&& !thisPathPart.equals(pathPart)
|
||||
&& !thisPathPart.equals("*")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// All parts matched
|
||||
return true;
|
||||
} else {
|
||||
// Number of "path parts" not the same
|
||||
// check wild card:
|
||||
if (this.path.endsWith("*")) {
|
||||
if (pathSize == (thisPathSize - 1) && (path.endsWith("/"))) {
|
||||
// Hack for making wildcards work with trailing slash
|
||||
pathList.add("");
|
||||
pathList.add("");
|
||||
pathSize += 2;
|
||||
}
|
||||
|
||||
if (thisPathSize < pathSize) {
|
||||
for (int i = 0; i < thisPathSize; i++) {
|
||||
String thisPathPart = thisPathList.get(i);
|
||||
String pathPart = pathList.get(i);
|
||||
if (thisPathPart.equals("*") && (i == thisPathSize - 1) && this.path.endsWith("*")) {
|
||||
// wildcard match
|
||||
return true;
|
||||
}
|
||||
if (!thisPathPart.startsWith(":")
|
||||
&& !thisPathPart.equals(pathPart)
|
||||
&& !thisPathPart.equals("*")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// All parts matched
|
||||
return true;
|
||||
}
|
||||
// End check wild card
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return httpMethod.name() + ", " + path + ", " + target;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package io.jenkins.blueocean.rest.router;
|
||||
|
||||
/**
|
||||
*
|
||||
* Copied from <a href="https://github.com/perwendel/spark">Sinatra inspired Spark</a>
|
||||
*
|
||||
* @author Per Wendel
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
public class RouteMatch {
|
||||
|
||||
private final Route target;
|
||||
private final String matchUri;
|
||||
private final String requestURI;
|
||||
private final String acceptType;
|
||||
|
||||
public RouteMatch(Route target, String matchUri, String requestUri, String acceptType) {
|
||||
super();
|
||||
this.target = target;
|
||||
this.matchUri = matchUri;
|
||||
this.requestURI = requestUri;
|
||||
this.acceptType = acceptType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the accept type
|
||||
*/
|
||||
public String getAcceptType() {
|
||||
return acceptType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the target
|
||||
*/
|
||||
public Route getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the matchUri
|
||||
*/
|
||||
public String getMatchUri() {
|
||||
return matchUri;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the requestUri
|
||||
*/
|
||||
public String getRequestURI() {
|
||||
return requestURI;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
package io.jenkins.blueocean.rest.router;
|
||||
|
||||
import io.jenkins.blueocean.rest.HttpMethod;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Simple route matcher that is supposed to work exactly as Sinatra's
|
||||
*
|
||||
* @author Per Wendel
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
/** package */
|
||||
class RouteMatcher {
|
||||
|
||||
private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(RouteMatcher.class);
|
||||
private static final char SINGLE_QUOTE = '\'';
|
||||
|
||||
private final List<RouteEntry> routes = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Parse and validates a route and adds it
|
||||
*
|
||||
* @param target the invocation target
|
||||
*/
|
||||
public void parseValidateAddRoute(HttpMethod method, Route target) {
|
||||
String route = target.path();
|
||||
String acceptType = target.accessType();
|
||||
addRoute(method, route, acceptType, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* finds target for a requested route
|
||||
*
|
||||
* @param httpMethod the http method
|
||||
* @param path the path
|
||||
* @param acceptType the accept type
|
||||
* @return the target
|
||||
*/
|
||||
public RouteMatch findTargetForRequestedRoute(HttpMethod httpMethod, String path, String acceptType) {
|
||||
List<RouteEntry> routeEntries = this.findTargetsForRequestedRoute(httpMethod, path);
|
||||
RouteEntry entry = findTargetWithGivenAcceptType(routeEntries, acceptType);
|
||||
return entry != null ? new RouteMatch(entry.target, entry.path, path, acceptType) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* ¨Clear all routes
|
||||
*/
|
||||
public void clearRoutes() {
|
||||
routes.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a particular route from the collection of those that have been previously routed.
|
||||
* Search for a previously established routes using the given path and HTTP method, removing
|
||||
* any matches that are found.
|
||||
*
|
||||
* @param path the route path
|
||||
* @param httpMethod the http method
|
||||
* @return <tt>true</tt> if this a matching route has been previously routed
|
||||
* @throws IllegalArgumentException if <tt>path</tt> is null or blank or if <tt>httpMethod</tt> is null, blank
|
||||
* or an invalid HTTP method
|
||||
* @since 2.2
|
||||
*/
|
||||
public boolean removeRoute(String path, String httpMethod) {
|
||||
if (StringUtils.isEmpty(path)) {
|
||||
throw new IllegalArgumentException("path cannot be null or blank");
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(httpMethod)) {
|
||||
throw new IllegalArgumentException("httpMethod cannot be null or blank");
|
||||
}
|
||||
|
||||
// Catches invalid input and throws IllegalArgumentException
|
||||
HttpMethod method = HttpMethod.valueOf(httpMethod);
|
||||
|
||||
return removeRoute(method, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a particular route from the collection of those that have been previously routed.
|
||||
* Search for a previously established routes using the given path and removes any matches that are found.
|
||||
*
|
||||
* @param path the route path
|
||||
* @return <tt>true</tt> if this a matching route has been previously routed
|
||||
* @throws java.lang.IllegalArgumentException if <tt>path</tt> is null or blank
|
||||
* @since 2.2
|
||||
*/
|
||||
public boolean removeRoute(String path) {
|
||||
if (StringUtils.isEmpty(path)) {
|
||||
throw new IllegalArgumentException("path cannot be null or blank");
|
||||
}
|
||||
|
||||
return removeRoute((HttpMethod) null, path);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// PRIVATE METHODS
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
private void addRoute(HttpMethod method, String url, String acceptedType, Route target) {
|
||||
RouteEntry entry = new RouteEntry(method, url, acceptedType, target);
|
||||
LOG.debug("Adds route: " + entry);
|
||||
// Adds to end of list
|
||||
routes.add(entry);
|
||||
}
|
||||
|
||||
//can be cached? I don't think so.
|
||||
private Map<String, RouteEntry> getAcceptedMimeTypes(List<RouteEntry> routes) {
|
||||
Map<String, RouteEntry> acceptedTypes = new HashMap<>();
|
||||
|
||||
for (RouteEntry routeEntry : routes) {
|
||||
if (!acceptedTypes.containsKey(routeEntry.acceptedType)) {
|
||||
acceptedTypes.put(routeEntry.acceptedType, routeEntry);
|
||||
}
|
||||
}
|
||||
|
||||
return acceptedTypes;
|
||||
}
|
||||
|
||||
private boolean routeWithGivenAcceptType(String bestMatch) {
|
||||
return !MimeParser.NO_MIME_TYPE.equals(bestMatch);
|
||||
}
|
||||
|
||||
private List<RouteEntry> findTargetsForRequestedRoute(HttpMethod httpMethod, String path) {
|
||||
List<RouteEntry> matchSet = new ArrayList<RouteEntry>();
|
||||
for (RouteEntry entry : routes) {
|
||||
if (entry.matches(httpMethod, path)) {
|
||||
matchSet.add(entry);
|
||||
}
|
||||
}
|
||||
return matchSet;
|
||||
}
|
||||
|
||||
private RouteEntry findTargetWithGivenAcceptType(List<RouteEntry> routeMatches, String acceptType) {
|
||||
if (acceptType != null && routeMatches.size() > 0) {
|
||||
Map<String, RouteEntry> acceptedMimeTypes = getAcceptedMimeTypes(routeMatches);
|
||||
String bestMatch = MimeParser.bestMatch(acceptedMimeTypes.keySet(), acceptType);
|
||||
|
||||
if (routeWithGivenAcceptType(bestMatch)) {
|
||||
return acceptedMimeTypes.get(bestMatch);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
if (routeMatches.size() > 0) {
|
||||
return routeMatches.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean removeRoute(HttpMethod httpMethod, String path) {
|
||||
List<RouteEntry> forRemoval = new ArrayList<>();
|
||||
|
||||
for (RouteEntry routeEntry : routes) {
|
||||
HttpMethod httpMethodToMatch = httpMethod;
|
||||
|
||||
if (httpMethod == null) {
|
||||
// Use the routeEntry's HTTP method if none was given, so that only path is used to match.
|
||||
httpMethodToMatch = routeEntry.httpMethod;
|
||||
}
|
||||
|
||||
if (routeEntry.matches(httpMethodToMatch, path)) {
|
||||
LOG.debug("Removing path {}", path, httpMethod == null ? "" : " with HTTP method " + httpMethod);
|
||||
|
||||
forRemoval.add(routeEntry);
|
||||
}
|
||||
}
|
||||
|
||||
return routes.removeAll(forRemoval);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
package io.jenkins.blueocean.rest.router;
|
||||
|
||||
import io.jenkins.blueocean.commons.ServiceException;
|
||||
import io.jenkins.blueocean.rest.HttpMethod;
|
||||
import io.jenkins.blueocean.rest.Request;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
public class Router {
|
||||
private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(RouteMatcher.class);
|
||||
private static final char SINGLE_QUOTE = '\'';
|
||||
|
||||
private final RouteMatcher routeMatcher = new RouteMatcher();
|
||||
|
||||
private final List<RouteEntry> routes = new ArrayList<>();
|
||||
|
||||
private Router() {
|
||||
}
|
||||
|
||||
public static final Router INSTANCE = new Router();
|
||||
|
||||
/**
|
||||
* Adds a route
|
||||
*
|
||||
* @param httpMethod the HTTP method
|
||||
* @param route the route implementation
|
||||
*/
|
||||
public void addRoute(HttpMethod httpMethod, Route route){
|
||||
routeMatcher.parseValidateAddRoute(httpMethod, route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a filter
|
||||
*
|
||||
* @param httpMethod the HTTP method
|
||||
* @param filter the route implementation
|
||||
*/
|
||||
public void addFilter(HttpMethod httpMethod, Route filter){
|
||||
routeMatcher.parseValidateAddRoute(httpMethod, filter);
|
||||
}
|
||||
|
||||
|
||||
public static void get(Route route){
|
||||
INSTANCE.addRoute(HttpMethod.get, route);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void post(Route route){
|
||||
INSTANCE.addRoute(HttpMethod.post, route);
|
||||
}
|
||||
|
||||
|
||||
public static void put(Route route){
|
||||
INSTANCE.addRoute(HttpMethod.put, route);
|
||||
}
|
||||
|
||||
public static void patch(Route route){
|
||||
INSTANCE.addRoute(HttpMethod.patch, route);
|
||||
}
|
||||
|
||||
public static void head(Route route){
|
||||
INSTANCE.addRoute(HttpMethod.head, route);
|
||||
}
|
||||
|
||||
public RouteMatcher routeMatcher(){
|
||||
return routeMatcher;
|
||||
}
|
||||
|
||||
public static void execute(RouteContext context){
|
||||
Object content=null;
|
||||
|
||||
RouteMatch match = context.routeMatcher().findTargetForRequestedRoute(context.httpMethod(), context.uri(), context.acceptType());
|
||||
|
||||
Route target = null;
|
||||
if (match != null) {
|
||||
target = match.getTarget();
|
||||
} else if (context.httpMethod() == HttpMethod.head && context.body().notSet()) {
|
||||
// See if get is mapped to provide default head mapping
|
||||
match = context.routeMatcher().findTargetForRequestedRoute(HttpMethod.get, context.uri(), context.acceptType());
|
||||
content = match != null ? "" : null;
|
||||
}
|
||||
|
||||
|
||||
if (target == null) {
|
||||
throw new ServiceException.NotFoundException(
|
||||
String.format("No route found for requested resource %s with HTTP method %s",
|
||||
context.uri(), context.httpMethod()));
|
||||
}
|
||||
|
||||
Request request = new Request(match, context.httpRequest());
|
||||
Object result = target.handle(request, context.response());;
|
||||
|
||||
if (result != null) {
|
||||
content = result;
|
||||
}
|
||||
context.body().set(content);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<?jelly escape-by-default='true'?>
|
||||
<div>
|
||||
This plugin is a part of Blue Ocean UI
|
||||
</div>
|
|
@ -1,7 +1,8 @@
|
|||
package io.jenkins.blueocean;
|
||||
|
||||
import hudson.Extension;
|
||||
import io.jenkins.embryo.App;
|
||||
import hudson.ExtensionList;
|
||||
import jenkins.model.Jenkins;
|
||||
import org.kohsuke.stapler.HttpResponse;
|
||||
import org.kohsuke.stapler.HttpResponses;
|
||||
|
||||
|
@ -15,4 +16,18 @@ public class BlueOceanUI {
|
|||
public HttpResponse doHello() {
|
||||
return HttpResponses.plainText("Hello wolrd!");
|
||||
}
|
||||
|
||||
public Object getRest(){
|
||||
try {
|
||||
ExtensionList extensions = Jenkins.getActiveInstance().getExtensionList("io.jenkins.blueocean.rest.ApiHead");
|
||||
if(extensions.size() > 0){
|
||||
return extensions.get(0);
|
||||
}else{
|
||||
return HttpResponses.notFound();
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
return HttpResponses.notFound();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<?jelly escape-by-default='true'?>
|
||||
<div>
|
||||
Blue Ocean core
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# Run embedded-driver
|
||||
|
||||
cd embedded-driver
|
||||
mvn hpi:run
|
||||
|
||||
This will launch jenkins with BO plugin.
|
||||
|
||||
### BlueOcean UI is available at:
|
||||
|
||||
http://localhost:8080/jenkins/bo
|
||||
|
||||
> Jenkins classic UI is available at http://localhost:8080/jenkins/
|
||||
|
||||
### BlueOcean rest API base URL is:
|
||||
|
||||
http://localhost:8080/jenkins/bo/rest
|
|
@ -14,22 +14,22 @@
|
|||
<name>BlueOcean :: BlueOcean Embedded Service API Driver </name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>blueocean-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>blueocean-commons</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>blueocean-security-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>blueocean-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>blueocean-commons</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>blueocean-security-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>profile-service-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
|
@ -39,5 +39,26 @@
|
|||
<artifactId>pipeline-service-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>blueocean-rest</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>blueocean-rest</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jenkins-ci.plugins</groupId>
|
||||
<artifactId>mailer</artifactId>
|
||||
<version>1.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jayway.restassured</groupId>
|
||||
<artifactId>rest-assured</artifactId>
|
||||
<version>2.8.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
@ -10,10 +10,12 @@ import jenkins.model.Jenkins;
|
|||
*/
|
||||
public abstract class AbstractEmbeddedService {
|
||||
|
||||
protected final Jenkins jenkins = Jenkins.getActiveInstance();
|
||||
protected Jenkins getJenkins(){
|
||||
return Jenkins.getActiveInstance();
|
||||
};
|
||||
|
||||
protected void validateOrganization(String organization){
|
||||
if (!organization.equals(Jenkins.getActiveInstance().getDisplayName())) {
|
||||
if (!organization.equals(Jenkins.getActiveInstance().getDisplayName().toLowerCase())) {
|
||||
throw new ServiceException.UnprocessableEntityException(String.format("Organization %s not found",
|
||||
organization));
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
package io.jenkins.blueocean;
|
||||
package io.jenkins.blueocean.service.embedded;
|
||||
|
||||
import hudson.Extension;
|
||||
import hudson.model.RootAction;
|
||||
import io.jenkins.blueocean.BlueOceanUI;
|
||||
import org.kohsuke.stapler.StaplerProxy;
|
||||
|
||||
import javax.inject.Inject;
|
|
@ -1,6 +1,8 @@
|
|||
package io.jenkins.blueocean.service.embedded;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import hudson.Extension;
|
||||
import hudson.model.Job;
|
||||
import hudson.model.Project;
|
||||
import hudson.model.Result;
|
||||
import hudson.util.RunList;
|
||||
|
@ -13,11 +15,10 @@ import io.jenkins.blueocean.api.pipeline.GetPipelineResponse;
|
|||
import io.jenkins.blueocean.api.pipeline.GetPipelineRunRequest;
|
||||
import io.jenkins.blueocean.api.pipeline.GetPipelineRunResponse;
|
||||
import io.jenkins.blueocean.api.pipeline.PipelineService;
|
||||
import io.jenkins.blueocean.api.pipeline.model.JobResult;
|
||||
import io.jenkins.blueocean.api.pipeline.model.Pipeline;
|
||||
import io.jenkins.blueocean.api.pipeline.model.Run;
|
||||
import io.jenkins.blueocean.security.Identity;
|
||||
import io.jenkins.blueocean.commons.ServiceException;
|
||||
import io.jenkins.blueocean.security.Identity;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.ArrayList;
|
||||
|
@ -39,7 +40,7 @@ public class EmbeddedPipelineService extends AbstractEmbeddedService implements
|
|||
public GetPipelineResponse getPipeline(@Nonnull Identity identity, @Nonnull GetPipelineRequest pipelineRequest) {
|
||||
validateOrganization(pipelineRequest.organization);
|
||||
|
||||
List<Project> projects = jenkins.getAllItems(Project.class);
|
||||
List<Project> projects = getJenkins().getAllItems(Project.class);
|
||||
for (Project project : projects) {
|
||||
if (project.getName().equals(pipelineRequest.pipeline)) {
|
||||
return new GetPipelineResponse(new Pipeline(pipelineRequest.organization, project.getName(),
|
||||
|
@ -55,15 +56,15 @@ public class EmbeddedPipelineService extends AbstractEmbeddedService implements
|
|||
public FindPipelinesResponse findPipelines(@Nonnull Identity identity, @Nonnull FindPipelinesRequest findPipelinesRequest) {
|
||||
validateOrganization(findPipelinesRequest.organization);
|
||||
|
||||
List<Project> projects = jenkins.getAllItems(Project.class);
|
||||
List<Job> projects = getJenkins().getAllItems(Job.class);
|
||||
List<Pipeline> pipelines = new ArrayList<Pipeline>();
|
||||
for (Project project : projects) {
|
||||
for (Job project : projects) {
|
||||
if (findPipelinesRequest.pipeline != null &&
|
||||
project.getName().contains(findPipelinesRequest.pipeline)) {
|
||||
|
||||
pipelines.add(new Pipeline(findPipelinesRequest.organization, project.getName(),
|
||||
Collections.EMPTY_LIST));
|
||||
!project.getName().contains(findPipelinesRequest.pipeline)) {
|
||||
continue;
|
||||
}
|
||||
pipelines.add(new Pipeline(findPipelinesRequest.organization, project.getName(),
|
||||
Collections.EMPTY_LIST));
|
||||
}
|
||||
return new FindPipelinesResponse(pipelines);
|
||||
}
|
||||
|
@ -73,18 +74,34 @@ public class EmbeddedPipelineService extends AbstractEmbeddedService implements
|
|||
public GetPipelineRunResponse getPipelineRun(@Nonnull Identity identity, @Nonnull GetPipelineRunRequest request) {
|
||||
validateOrganization(request.organization);
|
||||
|
||||
List<Project> projects = jenkins.getAllItems(Project.class);
|
||||
for (Project p : projects) {
|
||||
List<Job> projects = getJenkins().getAllItems(Job.class);
|
||||
for (Job p : projects) {
|
||||
if (!p.getName().equals(request.pipeline)) {
|
||||
continue;
|
||||
}
|
||||
RunList<? extends hudson.model.Run> runList = p.getBuilds();
|
||||
|
||||
hudson.model.Run run = null;
|
||||
if(request.run != null) {
|
||||
for (hudson.model.Run r : runList) {
|
||||
if (r.getId().equals(request.run)) {
|
||||
run = r;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(run == null){
|
||||
throw new ServiceException.NotFoundException(
|
||||
String.format("Run %s not found in organization %s and pipeline %s",
|
||||
request.run, request.organization, request.pipeline));
|
||||
}
|
||||
}else{
|
||||
run = runList.getLastBuild();
|
||||
}
|
||||
return new GetPipelineRunResponse(
|
||||
createBoRun(runList.getLastBuild(), request.organization, request.pipeline));
|
||||
createBoRun(run.getClass().getSimpleName(),run, request.organization, request.pipeline));
|
||||
}
|
||||
throw new ServiceException.NotFoundException(String.format("No run found for organization %s, pipeline: %s",
|
||||
request.organization, request.pipeline));
|
||||
throw new ServiceException.NotFoundException(String.format("Run id %s not found for organization %s, pipeline: %s",
|
||||
request.run, request.organization, request.pipeline));
|
||||
|
||||
}
|
||||
|
||||
|
@ -94,8 +111,8 @@ public class EmbeddedPipelineService extends AbstractEmbeddedService implements
|
|||
validateOrganization(request.organization);
|
||||
|
||||
List<Run> runs = new ArrayList<Run>();
|
||||
List<Project> projects = jenkins.getAllItems(Project.class);
|
||||
for (Project p : projects) {
|
||||
List<Job> projects = getJenkins().getAllItems(Job.class);
|
||||
for (Job p : projects) {
|
||||
if (request.pipeline != null && !p.getName().equals(request.pipeline)) {
|
||||
continue;
|
||||
}
|
||||
|
@ -103,7 +120,7 @@ public class EmbeddedPipelineService extends AbstractEmbeddedService implements
|
|||
if (request.latestOnly) {
|
||||
hudson.model.Run r = p.getLastBuild();
|
||||
if(r != null) {
|
||||
Run run = createBoRun(r, request.organization, p.getName());
|
||||
Run run = createBoRun(r.getClass().getSimpleName(),r, request.organization, p.getName());
|
||||
runs.add(run);
|
||||
}else{
|
||||
return new FindPipelineRunsResponse(runs,null, null);
|
||||
|
@ -113,14 +130,15 @@ public class EmbeddedPipelineService extends AbstractEmbeddedService implements
|
|||
|
||||
Iterator<? extends hudson.model.Run> iterator = runList.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
runs.add(createBoRun(iterator.next(), request.organization, p.getName()));
|
||||
hudson.model.Run r = iterator.next();
|
||||
runs.add(createBoRun(r.getClass().getSimpleName(),r, request.organization, p.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return new FindPipelineRunsResponse(runs, null, null);
|
||||
}
|
||||
|
||||
private Run createBoRun(hudson.model.Run r, String organization, String pipeline) {
|
||||
private Run createBoRun(String buildClass, hudson.model.Run r, String organization, String pipeline) {
|
||||
Date endTime = null;
|
||||
if (!r.isBuilding()) {
|
||||
endTime = new Date(r.getStartTimeInMillis() + r.getDuration());
|
||||
|
@ -133,8 +151,7 @@ public class EmbeddedPipelineService extends AbstractEmbeddedService implements
|
|||
.durationInMillis(r.getDuration())
|
||||
.status(getStatusFromJenkinsRun(r))
|
||||
.runSummary(r.getBuildStatusSummary().message)
|
||||
//TODO: need to determine how to check if it's pipeline build
|
||||
.result(new JobResult())
|
||||
.result(new io.jenkins.blueocean.api.pipeline.model.Result(buildClass, ImmutableMap.<String, Object>of()))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package io.jenkins.blueocean.service.embedded;
|
||||
|
||||
import hudson.Extension;
|
||||
import hudson.tasks.Mailer;
|
||||
import io.jenkins.blueocean.api.profile.CreateOrganizationRequest;
|
||||
import io.jenkins.blueocean.api.profile.CreateOrganizationResponse;
|
||||
import io.jenkins.blueocean.api.profile.FindUsersRequest;
|
||||
|
@ -15,9 +16,9 @@ import io.jenkins.blueocean.api.profile.ProfileService;
|
|||
import io.jenkins.blueocean.api.profile.model.Organization;
|
||||
import io.jenkins.blueocean.api.profile.model.User;
|
||||
import io.jenkins.blueocean.api.profile.model.UserDetails;
|
||||
import io.jenkins.blueocean.commons.ServiceException;
|
||||
import io.jenkins.blueocean.security.Identity;
|
||||
import io.jenkins.blueocean.security.LoginDetails;
|
||||
import io.jenkins.blueocean.commons.ServiceException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.ArrayList;
|
||||
|
@ -53,15 +54,15 @@ public class EmbeddedProfileService extends AbstractEmbeddedService implements P
|
|||
public GetUserDetailsResponse getUserDetails(@Nonnull Identity identity, @Nonnull GetUserDetailsRequest request) {
|
||||
hudson.model.User user = hudson.model.User.get(request.id, false, Collections.EMPTY_MAP);
|
||||
if (user == null) {
|
||||
throw new ServiceException.NotFoundException(String.format("Request user %s not found", request.id));
|
||||
throw new ServiceException.NotFoundException(String.format("Requested user %s not found", request.id));
|
||||
}
|
||||
|
||||
if(!user.getId().equals(request.id)){
|
||||
throw new ServiceException.NotFoundException(String.format("User %s not found", request.id));
|
||||
}
|
||||
|
||||
//TODO: How to get user's email in Jenkins
|
||||
return new GetUserDetailsResponse(new UserDetails(user.getId(), user.getFullName(),"none",
|
||||
return new GetUserDetailsResponse(new UserDetails(user.getId(), user.getFullName(),
|
||||
user.getProperty(Mailer.UserProperty.class).getAddress(),
|
||||
Collections.<LoginDetails>emptySet()));
|
||||
}
|
||||
|
||||
|
@ -69,7 +70,7 @@ public class EmbeddedProfileService extends AbstractEmbeddedService implements P
|
|||
@Override
|
||||
public GetOrganizationResponse getOrganization(@Nonnull Identity identity, @Nonnull GetOrganizationRequest request) {
|
||||
validateOrganization(request.name);
|
||||
return new GetOrganizationResponse(new Organization(jenkins.getDisplayName()));
|
||||
return new GetOrganizationResponse(new Organization(getJenkins().getDisplayName().toLowerCase()));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
package io.jenkins.blueocean.service.embedded;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.jayway.restassured.RestAssured;
|
||||
import com.jayway.restassured.response.Response;
|
||||
import com.jayway.restassured.response.ValidatableResponse;
|
||||
import hudson.model.FreeStyleBuild;
|
||||
import hudson.model.FreeStyleProject;
|
||||
import hudson.tasks.Shell;
|
||||
import io.jenkins.blueocean.commons.JsonConverter;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.jvnet.hudson.test.JenkinsRule;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
/**
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
public class EmbeddedPipelineServiceRestTest{
|
||||
|
||||
@Rule
|
||||
public JenkinsRule j = new JenkinsRule();
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
RestAssured.baseURI = j.jenkins.getRootUrl()+"bo/rest";
|
||||
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPipelineTest() throws IOException {
|
||||
j.createFreeStyleProject("pipeline1");
|
||||
|
||||
RestAssured.given().log().all().get("/organizations/jenkins/pipelines/pipeline1")
|
||||
.then().log().all()
|
||||
.statusCode(200)
|
||||
.body("pipeline.organization", Matchers.equalTo("jenkins"))
|
||||
.body("pipeline.name", Matchers.equalTo("pipeline1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findPipelinesTest() throws IOException {
|
||||
FreeStyleProject p1 = j.createFreeStyleProject("pipeline2");
|
||||
FreeStyleProject p2 = j.createFreeStyleProject("pipeline3");
|
||||
|
||||
RestAssured.given().log().all().get("/search?q=type:pipeline;organization:jenkins")
|
||||
.then().log().all()
|
||||
.statusCode(200)
|
||||
.body("pipelines[0].organization", Matchers.equalTo("jenkins"))
|
||||
.body("pipelines[0].name", Matchers.equalTo(p1.getName()))
|
||||
.body("pipelines[1].organization", Matchers.equalTo("jenkins"))
|
||||
.body("pipelines[1].name", Matchers.equalTo(p2.getName()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPipelineRunTest() throws Exception {
|
||||
FreeStyleProject p = j.createFreeStyleProject("pipeline4");
|
||||
p.getBuildersList().add(new Shell("echo hello!\nsleep 1"));
|
||||
FreeStyleBuild b = p.scheduleBuild2(0).get();
|
||||
j.assertBuildStatusSuccess(b);
|
||||
RestAssured.given().log().all().get("/organizations/jenkins/pipelines/pipeline4/runs/"+b.getId())
|
||||
.then().log().all()
|
||||
.statusCode(200)
|
||||
.body("run.id", Matchers.equalTo(b.getId()))
|
||||
.body("run.pipeline", Matchers.equalTo(p.getName()))
|
||||
.body("run.pipeline", Matchers.equalTo(p.getName()))
|
||||
.body("run.organization", Matchers.equalTo("jenkins"))
|
||||
.body("run.startTime", Matchers.equalTo(
|
||||
new SimpleDateFormat(JsonConverter.DATE_FORMAT_STRING).format(new Date(b.getStartTimeInMillis()))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPipelineRunLatestTest() throws Exception {
|
||||
FreeStyleProject p = j.createFreeStyleProject("pipeline5");
|
||||
p.getBuildersList().add(new Shell("echo hello!\nsleep 1"));
|
||||
FreeStyleBuild b = p.scheduleBuild2(0).get();
|
||||
j.assertBuildStatusSuccess(b);
|
||||
|
||||
RestAssured.given().log().all().get("/organizations/jenkins/pipelines/pipeline5/runs/?latestOnly=true")
|
||||
.then().log().all()
|
||||
.statusCode(200)
|
||||
.body("runs[0].id", Matchers.equalTo(b.getId()))
|
||||
.body("runs[0].pipeline", Matchers.equalTo(p.getName()))
|
||||
.body("runs[0].pipeline", Matchers.equalTo(p.getName()))
|
||||
.body("runs[0].organization", Matchers.equalTo("jenkins"))
|
||||
.body("runs[0].startTime", Matchers.equalTo(
|
||||
new SimpleDateFormat(JsonConverter.DATE_FORMAT_STRING).format(new Date(b.getStartTimeInMillis()))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPipelineRunsTest() throws Exception {
|
||||
FreeStyleProject p = j.createFreeStyleProject("pipeline6");
|
||||
p.getBuildersList().add(new Shell("echo hello!\nsleep 1"));
|
||||
FreeStyleBuild b = p.scheduleBuild2(0).get();
|
||||
j.assertBuildStatusSuccess(b);
|
||||
System.out.println(new SimpleDateFormat(JsonConverter.DATE_FORMAT_STRING).format(new Date(b.getStartTimeInMillis())));
|
||||
RestAssured.given().log().all().get("/organizations/jenkins/pipelines/pipeline6/runs")
|
||||
.then().log().all()
|
||||
.statusCode(200)
|
||||
.body("runs[0].id", Matchers.equalTo(b.getId()))
|
||||
.body("runs[0].pipeline", Matchers.equalTo(p.getName()))
|
||||
.body("runs[0].organization", Matchers.equalTo("jenkins"))
|
||||
.body("runs[0].startTime", Matchers.equalTo(
|
||||
new SimpleDateFormat(JsonConverter.DATE_FORMAT_STRING).format(new Date(b.getStartTimeInMillis()))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findPipelineRunsForAPipelineTest() throws Exception {
|
||||
FreeStyleProject p1 = j.createFreeStyleProject("pipeline1");
|
||||
FreeStyleProject p2 = j.createFreeStyleProject("pipeline2");
|
||||
p1.getBuildersList().add(new Shell("echo hello!\nsleep 1"));
|
||||
p2.getBuildersList().add(new Shell("echo hello!\nsleep 1"));
|
||||
Stack<FreeStyleBuild> builds = new Stack<FreeStyleBuild>();
|
||||
FreeStyleBuild b11 = p1.scheduleBuild2(0).get();
|
||||
FreeStyleBuild b12 = p1.scheduleBuild2(0).get();
|
||||
builds.push(b11);
|
||||
builds.push(b12);
|
||||
|
||||
j.assertBuildStatusSuccess(b11);
|
||||
j.assertBuildStatusSuccess(b12);
|
||||
|
||||
ValidatableResponse response = RestAssured.given().log().all().get("/search?q=type:run;organization:jenkins;pipeline=pipeline1")
|
||||
.then().log().all()
|
||||
.statusCode(200);
|
||||
|
||||
for (int i = 0; i < builds.size(); i++) {
|
||||
FreeStyleBuild b = builds.pop();
|
||||
response.body(String.format("runs[%s].id",i), Matchers.equalTo(b.getId()))
|
||||
.body(String.format("runs[%s].pipeline",i), Matchers.equalTo(b.getParent().getName()))
|
||||
.body(String.format("runs[%s].organization",i), Matchers.equalTo("jenkins"))
|
||||
.body(String.format("runs[%s].startTime",i), Matchers.equalTo(
|
||||
new SimpleDateFormat(JsonConverter.DATE_FORMAT_STRING).format(new Date(b.getStartTimeInMillis()))));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findPipelineRunsForAllPipelineTest() throws IOException, ExecutionException, InterruptedException {
|
||||
FreeStyleProject p1 = j.createFreeStyleProject("pipeline11");
|
||||
FreeStyleProject p2 = j.createFreeStyleProject("pipeline22");
|
||||
p1.getBuildersList().add(new Shell("echo hello!\nsleep 1"));
|
||||
p2.getBuildersList().add(new Shell("echo hello!\nsleep 1"));
|
||||
Stack<FreeStyleBuild> p1builds = new Stack<FreeStyleBuild>();
|
||||
p1builds.push(p1.scheduleBuild2(0).get());
|
||||
p1builds.push(p1.scheduleBuild2(0).get());
|
||||
|
||||
Stack<FreeStyleBuild> p2builds = new Stack<FreeStyleBuild>();
|
||||
p2builds.push(p2.scheduleBuild2(0).get());
|
||||
p2builds.push(p2.scheduleBuild2(0).get());
|
||||
|
||||
Map<String, Stack<FreeStyleBuild>> buildMap = ImmutableMap.of(p1.getName(), p1builds, p2.getName(), p2builds);
|
||||
|
||||
Response r = RestAssured.given().log().all().get("/search?q=type:run;organization:jenkins");
|
||||
ValidatableResponse response = r
|
||||
.then().log().all()
|
||||
.statusCode(200);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
String pipeline = r.path(String.format("runs[%s].pipeline",i));
|
||||
FreeStyleBuild b = buildMap.get(pipeline).pop();
|
||||
response.body(String.format("runs[%s].id",i), Matchers.equalTo(b.getId()))
|
||||
.body(String.format("runs[%s].pipeline",i), Matchers.equalTo(pipeline))
|
||||
.body(String.format("runs[%s].organization",i), Matchers.equalTo("jenkins"))
|
||||
.body(String.format("runs[%s].startTime",i), Matchers.equalTo(
|
||||
new SimpleDateFormat(JsonConverter.DATE_FORMAT_STRING).format(new Date(b.getStartTimeInMillis()))));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package io.jenkins.blueocean.service.embedded;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.jayway.restassured.RestAssured;
|
||||
import hudson.model.FreeStyleBuild;
|
||||
import hudson.model.FreeStyleProject;
|
||||
import hudson.tasks.Shell;
|
||||
|
@ -13,8 +14,8 @@ import io.jenkins.blueocean.api.pipeline.GetPipelineResponse;
|
|||
import io.jenkins.blueocean.api.pipeline.GetPipelineRunRequest;
|
||||
import io.jenkins.blueocean.api.pipeline.GetPipelineRunResponse;
|
||||
import io.jenkins.blueocean.api.pipeline.PipelineService;
|
||||
import io.jenkins.blueocean.security.Identity;
|
||||
import io.jenkins.blueocean.commons.JsonConverter;
|
||||
import io.jenkins.blueocean.security.Identity;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
|
@ -38,38 +39,42 @@ public class EmbeddedPipelineServiceTest {
|
|||
public JenkinsRule j = new JenkinsRule();
|
||||
|
||||
@Before
|
||||
public void before(){
|
||||
public void before() {
|
||||
List<PipelineService> PipelineService = j.jenkins.getExtensionList(PipelineService.class);
|
||||
Assert.assertTrue(PipelineService.size() == 1);
|
||||
this.pipelineService = PipelineService.get(0);
|
||||
RestAssured.baseURI = j.jenkins.getRootUrl()+"rest";
|
||||
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPipelineTest() throws IOException {
|
||||
j.createFreeStyleProject("pipeline1");
|
||||
GetPipelineResponse response = pipelineService.getPipeline(Identity.ANONYMOUS,
|
||||
new GetPipelineRequest("Jenkins", "pipeline1"));
|
||||
new GetPipelineRequest("jenkins", "pipeline1"));
|
||||
Assert.assertNotNull(response);
|
||||
Assert.assertNotNull(response.pipeline);
|
||||
|
||||
Assert.assertEquals("pipeline1", response.pipeline.name);
|
||||
Assert.assertEquals("Jenkins", response.pipeline.organization);
|
||||
Assert.assertEquals("jenkins", response.pipeline.organization);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void findPipelinesTest() throws IOException {
|
||||
j.createFreeStyleProject("pipeline1");
|
||||
j.createFreeStyleProject("pipeline2");
|
||||
|
||||
FindPipelinesResponse response = pipelineService.findPipelines(Identity.ANONYMOUS,
|
||||
new FindPipelinesRequest("Jenkins", "pipeline"));
|
||||
new FindPipelinesRequest("jenkins", "pipeline"));
|
||||
Assert.assertNotNull(response);
|
||||
Assert.assertEquals(2, response.pipelines.size());
|
||||
|
||||
Assert.assertEquals("pipeline1", response.pipelines.get(0).name);
|
||||
Assert.assertEquals("Jenkins", response.pipelines.get(0).organization);
|
||||
Assert.assertEquals("jenkins", response.pipelines.get(0).organization);
|
||||
Assert.assertEquals("pipeline2", response.pipelines.get(1).name);
|
||||
Assert.assertEquals("Jenkins", response.pipelines.get(1).organization);
|
||||
Assert.assertEquals("jenkins", response.pipelines.get(1).organization);
|
||||
}
|
||||
|
||||
|
||||
|
@ -79,12 +84,12 @@ public class EmbeddedPipelineServiceTest {
|
|||
p.getBuildersList().add(new Shell("echo hello!\nsleep 1"));
|
||||
FreeStyleBuild b = p.scheduleBuild2(0).get();
|
||||
GetPipelineRunResponse response = pipelineService.getPipelineRun(Identity.ANONYMOUS,
|
||||
new GetPipelineRunRequest("Jenkins", "pipeline1"));
|
||||
new GetPipelineRunRequest("jenkins", "pipeline1", null));
|
||||
System.out.println(JsonConverter.toJson(response));
|
||||
Assert.assertNotNull(response);
|
||||
Assert.assertNotNull(response.run);
|
||||
Assert.assertEquals(b.getId(), response.run.id);
|
||||
Assert.assertEquals("Jenkins", response.run.organization);
|
||||
Assert.assertEquals("jenkins", response.run.organization);
|
||||
Assert.assertEquals("pipeline1", response.run.pipeline);
|
||||
Assert.assertEquals(b.getStartTimeInMillis(), response.run.startTime.getTime());
|
||||
}
|
||||
|
@ -101,15 +106,15 @@ public class EmbeddedPipelineServiceTest {
|
|||
builds.push(p1.scheduleBuild2(0).get());
|
||||
|
||||
FindPipelineRunsResponse response = pipelineService.findPipelineRuns(Identity.ANONYMOUS,
|
||||
new FindPipelineRunsRequest("Jenkins", "pipeline1", false, Collections.EMPTY_LIST, null, null));
|
||||
new FindPipelineRunsRequest("jenkins", "pipeline1", false, Collections.EMPTY_LIST, null, null));
|
||||
|
||||
System.out.println("Response: "+JsonConverter.toJson(response));
|
||||
System.out.println("Response: " + JsonConverter.toJson(response));
|
||||
Assert.assertNotNull(response);
|
||||
Assert.assertEquals(builds.size(), response.runs.size());
|
||||
for(int i = 0;i < response.runs.size(); i++){
|
||||
for (int i = 0; i < response.runs.size(); i++) {
|
||||
FreeStyleBuild b = builds.pop();
|
||||
Assert.assertEquals(b.getId(), response.runs.get(i).id);
|
||||
Assert.assertEquals("Jenkins", response.runs.get(i).organization);
|
||||
Assert.assertEquals("jenkins", response.runs.get(i).organization);
|
||||
Assert.assertEquals("pipeline1", response.runs.get(i).pipeline);
|
||||
Assert.assertEquals(b.getStartTimeInMillis(), response.runs.get(i).startTime.getTime());
|
||||
}
|
||||
|
@ -131,22 +136,22 @@ public class EmbeddedPipelineServiceTest {
|
|||
|
||||
Map<String, Stack<FreeStyleBuild>> buildMap = ImmutableMap.of(p1.getName(), p1builds, p2.getName(), p2builds);
|
||||
FindPipelineRunsResponse response = pipelineService.findPipelineRuns(Identity.ANONYMOUS,
|
||||
new FindPipelineRunsRequest("Jenkins", null, false, Collections.EMPTY_LIST, null, null));
|
||||
new FindPipelineRunsRequest("jenkins", null, false, Collections.EMPTY_LIST, null, null));
|
||||
|
||||
System.out.println("Response: "+JsonConverter.toJson(response));
|
||||
System.out.println("Response: " + JsonConverter.toJson(response));
|
||||
|
||||
Assert.assertNotNull(response);
|
||||
Assert.assertEquals(p1builds.size() + p2builds.size(), response.runs.size());
|
||||
for(int i = 0;i < response.runs.size(); i++){
|
||||
for (int i = 0; i < response.runs.size(); i++) {
|
||||
FreeStyleBuild b = buildMap.get(response.runs.get(i).pipeline).pop();
|
||||
Assert.assertEquals(b.getId(), response.runs.get(i).id);
|
||||
Assert.assertEquals("Jenkins", response.runs.get(i).organization);
|
||||
Assert.assertEquals("jenkins", response.runs.get(i).organization);
|
||||
Assert.assertEquals(b.getProject().getName(), response.runs.get(i).pipeline);
|
||||
Assert.assertEquals(b.getStartTimeInMillis(), response.runs.get(i).startTime.getTime());
|
||||
}
|
||||
}
|
||||
|
||||
public void getPipelineRunForWorkflow(){
|
||||
public void getPipelineRunForWorkflow() {
|
||||
// WorkflowJob job = j.jenkins.createProject(WorkflowJob.class, "Noddy Job");
|
||||
//
|
||||
// String script = "node {" +
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
package io.jenkins.blueocean.service.embedded;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.jayway.restassured.RestAssured;
|
||||
import com.jayway.restassured.response.Response;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.jvnet.hudson.test.JenkinsRule;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
public class EmbeddedProfileServiceRestTest {
|
||||
@Rule
|
||||
public JenkinsRule j = new JenkinsRule();
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
RestAssured.baseURI = j.jenkins.getRootUrl()+"bo/rest";
|
||||
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUserTest() throws Exception {
|
||||
|
||||
RestAssured.given().log().all().get("/users/"+j.jenkins.getUser("SYSTEM").getId())
|
||||
.then().log().all()
|
||||
.statusCode(200)
|
||||
.body("user.id", Matchers.equalTo(j.jenkins.getUser("SYSTEM").getId()))
|
||||
.body("user.name", Matchers.equalTo(j.jenkins.getUser("SYSTEM").getFullName()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUserDetailsTest() throws Exception {
|
||||
hudson.model.User user = j.jenkins.getUser("alice");
|
||||
|
||||
RestAssured.given().log().all().get("/users/"+user.getId()+"?details=true")
|
||||
.then().log().all()
|
||||
.statusCode(200)
|
||||
.body("user.id", Matchers.equalTo(user.getId()))
|
||||
.body("user.name", Matchers.equalTo(user.getFullName()))
|
||||
.body("user.email", Matchers.equalTo("none"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getOrganizationTest(){
|
||||
RestAssured.given().log().all().get("/organizations/jenkins")
|
||||
.then().log().all()
|
||||
.statusCode(200)
|
||||
.body("organization.name", Matchers.equalTo("jenkins"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void FindUsersTest() throws Exception {
|
||||
List<String> names = ImmutableList.of("alice", "bob");
|
||||
j.jenkins.getUser(names.get(0));
|
||||
j.jenkins.getUser(names.get(1));
|
||||
|
||||
Response response = RestAssured.given().log().all().get("/search?q=type:user;organization:jenkins");
|
||||
|
||||
response.then().log().all().statusCode(200);
|
||||
|
||||
Assert.assertTrue(names.contains((String)response.path("users[0].id")));
|
||||
Assert.assertTrue(names.contains((String)response.path("users[0].name")));
|
||||
Assert.assertTrue(names.contains((String)response.path("users[1].id")));
|
||||
Assert.assertTrue(names.contains((String)response.path("users[1].name")));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -10,9 +10,8 @@ import io.jenkins.blueocean.api.profile.GetUserRequest;
|
|||
import io.jenkins.blueocean.api.profile.GetUserResponse;
|
||||
import io.jenkins.blueocean.api.profile.ProfileService;
|
||||
import io.jenkins.blueocean.api.profile.model.User;
|
||||
import io.jenkins.blueocean.security.Identity;
|
||||
import io.jenkins.blueocean.commons.JsonConverter;
|
||||
import org.junit.After;
|
||||
import io.jenkins.blueocean.security.Identity;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
|
@ -28,6 +27,10 @@ public class EmbeddedProfileServiceTest {
|
|||
|
||||
private ProfileService profileService;
|
||||
|
||||
@Rule
|
||||
public JenkinsRule j = new JenkinsRule();
|
||||
|
||||
|
||||
@Before
|
||||
public void before(){
|
||||
List<ProfileService> profileServices = j.jenkins.getExtensionList(ProfileService.class);
|
||||
|
@ -35,13 +38,6 @@ public class EmbeddedProfileServiceTest {
|
|||
this.profileService = profileServices.get(0);
|
||||
}
|
||||
|
||||
@After
|
||||
public void after(){
|
||||
|
||||
}
|
||||
|
||||
@Rule
|
||||
public JenkinsRule j = new JenkinsRule();
|
||||
|
||||
@Test
|
||||
public void getUserTest() throws Exception {
|
||||
|
@ -51,9 +47,6 @@ public class EmbeddedProfileServiceTest {
|
|||
Assert.assertNotNull(response.user);
|
||||
Assert.assertEquals(response.user.id, "SYSTEM");
|
||||
Assert.assertEquals(response.user.name, "SYSTEM");
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -69,9 +62,9 @@ public class EmbeddedProfileServiceTest {
|
|||
@Test
|
||||
public void getOrganizationTest(){
|
||||
GetOrganizationResponse response = profileService.getOrganization(new Identity("alice"),
|
||||
new GetOrganizationRequest("Jenkins"));
|
||||
new GetOrganizationRequest("jenkins"));
|
||||
Assert.assertNotNull(response.organization);
|
||||
Assert.assertEquals(response.organization.name, "Jenkins");
|
||||
Assert.assertEquals(response.organization.name, "jenkins");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -80,7 +73,7 @@ public class EmbeddedProfileServiceTest {
|
|||
j.jenkins.getUser(names[0]);
|
||||
j.jenkins.getUser(names[1]);
|
||||
|
||||
FindUsersResponse response = profileService.findUsers(new Identity("alice"), new FindUsersRequest("Jenkins", null, null));
|
||||
FindUsersResponse response = profileService.findUsers(new Identity("alice"), new FindUsersRequest("jenkins", null, null));
|
||||
|
||||
System.out.println(JsonConverter.toJson(response));
|
||||
Assert.assertTrue(response.users.size() == 2);
|
||||
|
|
|
@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
|||
import io.jenkins.blueocean.security.Identity;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Request for {@link PipelineService#getPipelineRun(Identity, GetPipelineRunRequest)}
|
||||
|
@ -17,10 +18,15 @@ public final class GetPipelineRunRequest {
|
|||
@JsonProperty("pipeline")
|
||||
public final String pipeline;
|
||||
|
||||
/** run is run id, if null latest run is expected */
|
||||
@JsonProperty("run")
|
||||
public final String run;
|
||||
|
||||
public GetPipelineRunRequest(@Nonnull @JsonProperty("organization") String organization,
|
||||
@Nonnull @JsonProperty("pipeline") String pipeline) {
|
||||
@Nonnull @JsonProperty("pipeline") String pipeline,
|
||||
@Nullable @JsonProperty("run") String run) {
|
||||
this.organization = organization;
|
||||
this.pipeline = pipeline;
|
||||
|
||||
this.run = run;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,32 @@
|
|||
package io.jenkins.blueocean.api.pipeline.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Describes Job result
|
||||
*
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
public class JobResult implements Result {
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.JOB;
|
||||
public class JobResult{
|
||||
|
||||
@JsonProperty("data")
|
||||
private final Map<String, String> jobResultData;
|
||||
|
||||
public JobResult(@Nonnull @JsonProperty("data") Map<String, String> jobResultData) {
|
||||
this.jobResultData = jobResultData;
|
||||
}
|
||||
|
||||
//TODO: to be filled later based on how BO UI evolves
|
||||
|
||||
public String getType() {
|
||||
return "job";
|
||||
}
|
||||
|
||||
|
||||
public Map<String,String> getData() {
|
||||
return ImmutableMap.copyOf(jobResultData);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,31 @@
|
|||
package io.jenkins.blueocean.api.pipeline.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Abstract to describe Pipeline/workflow build result
|
||||
*
|
||||
* @author Vivek Pandey
|
||||
*/
|
||||
public class PipelineResult implements Result {
|
||||
@Override
|
||||
public Type getType() {
|
||||
public class PipelineResult{
|
||||
@JsonProperty("data")
|
||||
private final Map<String, Object> pipelineResultData;
|
||||
|
||||
public PipelineResult(@Nonnull @JsonProperty("data") Map<String, Object> data) {
|
||||
this.pipelineResultData = data;
|
||||
}
|
||||
|
||||
|
||||
public String getType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
//TODO: to be filled later based on how BO UI evolves
|
||||
|
||||
|
||||
public Map<String, Object> getData() {
|
||||
return pipelineResultData;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package io.jenkins.blueocean.api.pipeline.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Marker interface that describes build result
|
||||
|
@ -12,26 +14,15 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
|||
* @see JobResult
|
||||
* @see Run
|
||||
*/
|
||||
@JsonTypeInfo(
|
||||
use = JsonTypeInfo.Id.NAME,
|
||||
include = JsonTypeInfo.As.PROPERTY,
|
||||
property = "resultType")
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = JobResult.class, name = "JOB"),
|
||||
@JsonSubTypes.Type(value = PipelineResult.class, name = "PIPELINE") })
|
||||
public interface Result {
|
||||
public class Result{
|
||||
@JsonProperty("type")
|
||||
public final String type;
|
||||
|
||||
/** Result types, must be all names defined inside JsonSubTypes annotation */
|
||||
enum Type {JOB, PIPELINE};
|
||||
@JsonProperty("data")
|
||||
public final Map<String, ?> data;
|
||||
|
||||
/**
|
||||
* Gives what kind of Result this implements. This is to help a json processor client
|
||||
* to know what kind of Result it is from the JSON packet of {@link Run}
|
||||
*
|
||||
* We don't serialize it in json as Jackson already adds resultType object.
|
||||
*
|
||||
* @return One of {@link Type}
|
||||
*/
|
||||
@JsonIgnore
|
||||
Type getType();
|
||||
public Result(@Nonnull @JsonProperty("type") String type, @Nonnull @JsonProperty("data") Map<String, ?> data) {
|
||||
this.type = type;
|
||||
this.data = ImmutableMap.copyOf(data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,9 +65,6 @@ public final class Run {
|
|||
@JsonProperty("result")
|
||||
public final Result result;
|
||||
|
||||
@JsonProperty("resultType")
|
||||
public final Result.Type resultType;
|
||||
|
||||
public enum Status {
|
||||
/** Build completed successfully */
|
||||
SUCCESSFUL,
|
||||
|
@ -129,11 +126,11 @@ public final class Run {
|
|||
this.runTrend = runTrend;
|
||||
this.enQueueTime = new Date(enQueueTime.getTime());
|
||||
this.result = result;
|
||||
if(result != null) {
|
||||
this.resultType = result.getType();
|
||||
}else{
|
||||
this.resultType = null;
|
||||
}
|
||||
// if(result != null) {
|
||||
// this.resultType = result.getType();
|
||||
// }else{
|
||||
// this.resultType = null;
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
package io.jenkins.blueocean.api.pipeline;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import io.jenkins.blueocean.api.pipeline.model.JobResult;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.jenkins.blueocean.api.pipeline.model.Result;
|
||||
import io.jenkins.blueocean.api.pipeline.model.Run;
|
||||
import io.jenkins.blueocean.commons.JsonConverter;
|
||||
import org.junit.Assert;
|
||||
|
@ -26,7 +27,7 @@ public class FindPipelineRunsResponseTest {
|
|||
.durationInMillis(10000L)
|
||||
.runSummary("build sucessful")
|
||||
.runTrend(Run.RunTrend.FIXED)
|
||||
.result(new JobResult())
|
||||
.result(new Result("job", ImmutableMap.of("status", "success")))
|
||||
.build(),
|
||||
new Run.Builder("24","test1","cloudbees")
|
||||
.branch("master")
|
||||
|
@ -38,7 +39,7 @@ public class FindPipelineRunsResponseTest {
|
|||
.durationInMillis(10000L)
|
||||
.runSummary("build sucessful")
|
||||
.runTrend(Run.RunTrend.FIXED)
|
||||
.result(new JobResult())
|
||||
.result(new Result("job",ImmutableMap.of("status", "success")))
|
||||
.build()), null, null);
|
||||
|
||||
String json = JsonConverter.toJson(response);
|
||||
|
@ -64,7 +65,7 @@ public class FindPipelineRunsResponseTest {
|
|||
Assert.assertEquals(expected.enQueueTime, actual.enQueueTime);
|
||||
Assert.assertEquals(expected.runSummary, actual.runSummary);
|
||||
Assert.assertEquals(expected.runTrend, actual.runTrend);
|
||||
Assert.assertTrue(actual.result instanceof JobResult);
|
||||
// Assert.assertTrue(actual.result instanceof JobResult);
|
||||
}
|
||||
|
||||
System.out.println("Converted back from Json:\n"+JsonConverter.toJson(responseFromJson));
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package io.jenkins.blueocean.api.pipeline;
|
||||
|
||||
import io.jenkins.blueocean.api.pipeline.model.JobResult;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.jenkins.blueocean.api.pipeline.model.Result;
|
||||
import io.jenkins.blueocean.api.pipeline.model.Run;
|
||||
import io.jenkins.blueocean.commons.JsonConverter;
|
||||
import org.junit.Assert;
|
||||
|
@ -27,7 +28,7 @@ public class GetPipelineRunResponseTest {
|
|||
.durationInMillis(10000L)
|
||||
.runSummary("build sucessful")
|
||||
.runTrend(Run.RunTrend.FIXED)
|
||||
.result(new JobResult())
|
||||
.result(new Result("job",ImmutableMap.of("status", "success")))
|
||||
.build());
|
||||
|
||||
String json = JsonConverter.toJson(response);
|
||||
|
@ -50,7 +51,7 @@ public class GetPipelineRunResponseTest {
|
|||
Assert.assertEquals(expected.enQueueTime, actual.enQueueTime);
|
||||
Assert.assertEquals(expected.runSummary, actual.runSummary);
|
||||
Assert.assertEquals(expected.runTrend, actual.runTrend);
|
||||
Assert.assertTrue(actual.result instanceof JobResult);
|
||||
Assert.assertEquals(expected.result.type, actual.result.type);
|
||||
|
||||
System.out.println("Converted back from Json:\n"+JsonConverter.toJson(responseFromJson));
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import org.junit.Test;
|
|||
public class GetPipelineRunTest {
|
||||
@Test
|
||||
public void serializeDeserialize(){
|
||||
GetPipelineRunRequest request = new GetPipelineRunRequest("cloudbees", "test1");
|
||||
GetPipelineRunRequest request = new GetPipelineRunRequest("cloudbees", "test1", null);
|
||||
|
||||
String json = JsonConverter.toJson(request);
|
||||
|
||||
|
|
3
pom.xml
3
pom.xml
|
@ -16,7 +16,7 @@
|
|||
<name>Blue Ocean UI Parent</name>
|
||||
|
||||
<properties>
|
||||
<java.level>6</java.level>
|
||||
<java.level>7</java.level>
|
||||
<jackson.version>2.2.2</jackson.version>
|
||||
<jenkins.version>1.639</jenkins.version>
|
||||
<!--<jenkins.version>1.609.3</jenkins.version>-->
|
||||
|
@ -30,6 +30,7 @@
|
|||
<module>profile-service-api</module>
|
||||
<module>pipeline-service-api</module>
|
||||
<module>embedded-driver</module>
|
||||
<module>blueocean-rest</module>
|
||||
<module>alpha</module>
|
||||
<module>bravo</module>
|
||||
<module>war</module>
|
||||
|
|
|
@ -12,10 +12,10 @@ import javax.annotation.Nullable;
|
|||
* @author Vivek Pandey
|
||||
*/
|
||||
public final class GetUserDetailsResponse{
|
||||
@JsonProperty("userDetails")
|
||||
@JsonProperty("user")
|
||||
public final UserDetails userDetails;
|
||||
|
||||
public GetUserDetailsResponse(@Nullable @JsonProperty("userDetails")UserDetails userDetails) {
|
||||
public GetUserDetailsResponse(@Nullable @JsonProperty("user")UserDetails userDetails) {
|
||||
this.userDetails = userDetails;
|
||||
}
|
||||
}
|
||||
|
|
20
war/pom.xml
20
war/pom.xml
|
@ -69,6 +69,26 @@
|
|||
<type>hpi</type>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jenkins.blueocean</groupId>
|
||||
<artifactId>blueocean-rest</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>hpi</type>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jenkins.blueocean</groupId>
|
||||
<artifactId>embedded-driver</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>hpi</type>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>servlet-api</artifactId>
|
||||
<version>2.4</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
Loading…
Reference in New Issue