-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix forms download by providing an endpoint in the api gateway
- Loading branch information
1 parent
407d3ba
commit 3ca29ce
Showing
4 changed files
with
185 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
src/main/java/edu/stanford/protege/webprotege/gateway/FormsController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} |
74 changes: 74 additions & 0 deletions
74
src/main/java/edu/stanford/protege/webprotege/gateway/RpcClient.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
76 changes: 76 additions & 0 deletions
76
src/test/java/edu/stanford/protege/webprotege/gateway/RpcClientTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
|
||
} | ||
} |