Skip to content

Commit

Permalink
Fix forms download by providing an endpoint in the api gateway
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewhorridge committed Jun 15, 2024
1 parent 407d3ba commit 3ca29ce
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 1 deletion.
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);

}
}

0 comments on commit 3ca29ce

Please sign in to comment.