Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Fix forms download by providing an endpoint in the api gateway #8

Merged
merged 1 commit into from
Jun 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</parent>
<groupId>edu.stanford.protege</groupId>
<artifactId>webprotege-gwt-api-gateway</artifactId>
<version>1.0.7</version>
<version>1.0.8-SNAPSHOT</version>
<name>webprotege-gwt-api-gateway</name>
<description>The API Gateway for the WebProtégé GWT User Interface</description>
<properties>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package edu.stanford.protege.webprotege.gateway;

import edu.stanford.protege.webprotege.common.*;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

/**
* Matthew Horridge
* Stanford Center for Biomedical Informatics Research
* 2024-06-14
*/
@RestController
public class FormsController {

private static final RpcMethod GET_FORM_DESCRIPTORS = new RpcMethod("webprotege.forms.GetProjectFormDescriptors");

private static final String PROJECT_ID = "projectId";

private final RpcClient rpcClient;

public FormsController(RpcClient rpcClient) {
this.rpcClient = rpcClient;
}

@GetMapping("/data/projects/{projectId}/forms")
public ResponseEntity<Map<String, Object>> getForms(@PathVariable(PROJECT_ID) ProjectId projectId,
@AuthenticationPrincipal Jwt jwt) {
return rpcClient.call(jwt, GET_FORM_DESCRIPTORS, Map.of(PROJECT_ID, projectId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package edu.stanford.protege.webprotege.gateway;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import edu.stanford.protege.webprotege.common.UserId;
import org.jetbrains.annotations.NotNull;
import org.springframework.http.*;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ResponseStatusException;

import javax.annotation.Nonnull;
import java.util.Map;
import java.util.concurrent.ExecutionException;

/**
* Matthew Horridge
* Stanford Center for Biomedical Informatics Research
* 2024-06-14
*/
@Component
public class RpcClient {

private static final String PREFERRED_USERNAME = "preferred_username";

private final RpcRequestProcessor requestProcessor;

private final ObjectMapper objectMapper;

public RpcClient(RpcRequestProcessor requestProcessor, ObjectMapper objectMapper) {
this.requestProcessor = requestProcessor;
this.objectMapper = objectMapper;
}

/**
* Make a call using the specified RPC method and parameters
* @param token The access token that is used for authentication and authorization
* @param method The RPC method to call
* @param params The parameters for the method
* @return A response entity that represents the result, if successful, or an error if
* there was a failure.
*/
@Nonnull
public ResponseEntity<Map<String, Object>> call(@Nonnull Jwt token,
@Nonnull RpcMethod method,
@Nonnull Map<String, Object> params) {
var userIdClaim = token.getClaimAsString(PREFERRED_USERNAME);
var userId = UserId.valueOf(userIdClaim);
var paramsAsNode = serializeParams(params);
var rpcResponse = executeCall(token, method, paramsAsNode, userId);
var error = rpcResponse.error();
if(error != null) {
throw new ResponseStatusException(error.code(), error.message(), null);
}
return ResponseEntity.ok(rpcResponse.result());

}

private RpcResponse executeCall(@NotNull Jwt token, @NotNull RpcMethod method, ObjectNode paramsAsNode, UserId userId) {
try {
var response = requestProcessor.processRequest(new RpcRequest(method, paramsAsNode),
token.getTokenValue(), userId);
return response.get();
} catch (ExecutionException e) {
throw new ResponseStatusException(500, e.getCause().getMessage(), e.getCause());
} catch (Throwable t) {
throw new ResponseStatusException(500, t.getMessage(), t);
}
}

private ObjectNode serializeParams(Map<String, Object> params) {
return objectMapper.convertValue(params, ObjectNode.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package edu.stanford.protege.webprotege.gateway;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.*;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.server.ResponseStatusException;

import java.util.Map;
import java.util.concurrent.CompletableFuture;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class RpcClientTest {

private RpcClient client;

@Mock
private RpcRequestProcessor requestProcessor;

@Mock
ObjectMapper objectMapper;


@BeforeEach
void setUp() {
client = new RpcClient(requestProcessor,
objectMapper);
}

@Test
void shouldReturn200OkResult() {
// Given a normal completion
var resultMap = Map.<String, Object>of("foo", "bar");
var response = RpcResponse.forResult("theMethod", resultMap);
var future = CompletableFuture.completedFuture(response);
when(requestProcessor.processRequest(any(), any(), any()))
.thenReturn(future);
var result = client.call(mock(Jwt.class), mock(RpcMethod.class), Map.of());
// Result is 200 OK
assertThat(result.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(200));
assertThat(result.getBody()).isEqualTo(resultMap);
}

@Test
void shouldThrowExceptionOnRpcErrorCode() {
// Given an error completion
var response = RpcResponse.forError("TheErrorMessage", HttpStatus.valueOf(400));
var future = CompletableFuture.completedFuture(response);
when(requestProcessor.processRequest(any(), any(), any()))
.thenReturn(future);
var thrown = assertThrowsExactly(ResponseStatusException.class, () -> {
client.call(mock(Jwt.class), mock(RpcMethod.class), Map.of());
});
assertThat(thrown.getStatusCode().value()).isEqualTo(400);
}

@Test
void shouldThrow500ErrorResultOnInternalErrorFromCompletion() {
// Given an error completion
when(requestProcessor.processRequest(any(), any(), any()))
.thenThrow(new RuntimeException("Some internal error"));
var thrown = assertThrowsExactly(ResponseStatusException.class, () -> {
client.call(mock(Jwt.class), mock(RpcMethod.class), Map.of());
});
assertThat(thrown.getStatusCode().value()).isEqualTo(500);

}
}
Loading